Angular Custom Form Validation

Home / Angular Custom Form Validation

angular_small

It’s been a while, but I had some time today to work a bit more on my Angular2 multiselect implementation. The next aspect that I wanted to implement is validation.


Angular2’s FormModule is similar to Angular 1.x’s forms. One major difference is that, imho, Angular 1.x’s integration is more straight-forward. There is a bit of a learning curve involved with Angular2’s implementation.

In a typical scenario with a custom form input, ideally, there is an ngModel, we track changes against that model, and are alerted whether or not the values provided through the model are valid. For a dropdown, a ‘required’ attribute makes sense to implement.

With Angular2, we have to implement a few interfaces to achieve the desired work-flow.

First, we have to work with ngModel and the ControlValueAccessor API. Defining a ControlValueAccessor is straight-forward:

const MULTISELECT_VALUE_ACCESSOR = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => Multiselect),
  multi: true
};

That bit of code simply tells Angular that our class, Multiselect, will provide the NG_VALUE_ACCESSOR interface. The “forwardRef” is needed because the component won’t be defined yet in the initialization/flow. Then, we implement the interface in our Multiselect class.

export class Multiselect implements OnInit, ControlValueAccessor

	// ControlValueAccessor Interface and mutator
	private _onChange = (_: any) => {};
	private _onTouched = () => {};

	registerOnChange(fn: (value: any) => any): void { this._onChange = fn; console.log(fn); }
	registerOnTouched(fn: () => any): void { this._onTouched = fn; }

	writeValue(value: any) {
		console.log('writing value ' + value);
		if (value !== undefined) {
		  this._selectedItems = value;
		} else  {
		  this._selectedItems = [];
		}
	}
}

The (3) interface methods are self explanatory. I create private “mutators” so that I can have handles to the onChange/onTouched events. This makes it so that if I want to manually trigger a change, I can. This, in my mind, is akin to triggering a $digset cycle in Angular 1.x. The “writeValue” method is the only that is not entirely clear in terms of purpose. In the documentation, this method is intended to write the initial value only. Initially, I thought it was part of the change tracking, but it is not.

With the ControlValueAccessor implemented, the only other thing the Multiselect needed was to actually set the selected values and to trigger on-change. When an item is clicked, this method is fired:

  select(item: any) {
    item.checked = !item.checked;
    this._selectedItems = this._equalPipe.transform(this._items, {checked: true});
    this._onChange(this._selectedItems);
  }

Now you can see why I kept a handle to the on-change method. On a side-note, you can see that I’m executing the “PipeFilter” called EqualPipe directly. This is very similar to the filter service in Angular 1.x. In order to use a PipeFilter within your component, the only thing necessary is to add the pipe as a provider in your app.module. With the PipeFilter provider available, then the filter can be injected into your component’s constructor. For example, my app.module looks like this:

@NgModule({
  imports: [BrowserModule, FormsModule, ReactiveFormsModule, JsonpModule, NgbModule.forRoot()], 
  declarations: [ AppComponent, Multiselect, FilterPipe, EqualPipe ],
  bootstrap:    [ AppComponent ],
  providers: [ EqualPipe ] 
})

Finally, we can now look at the mark-up required to integrate with the FormModule.

<div class="form-group" [ngClass]="{'has-error':!itemsFormElement.valid}">
  <multiselect id="items" name="items" #itemsFormElement="ngModel" class="pull-left" [items]="items" [(ngModel)]="_selectedItems" (ngModelChange)="onChange($event)" required></multiselect>
  <small [hidden]="itemsFormElement.valid" class="form-text text-muted danger">You must select an item.</small>
</div>

Here I have added [ngClass] attributes displaying styles based on validity, added the required attribute, and added a small message to display when the form element is not valid. The form element is valid whenever anything is selected and invalid when nothing is selected (required).

The only thing that was odd about any of this is that you’ll notice the extra attribute called “#itemsFormElement” which is set equal to ngModel. We define this reference so that we get the form validity object and have a handle to it. With a handle to it, then we have a handle to the “valid” and other properties that the FormsModule provides.

All in all, it was pretty straight-forward, once I wrapped my head around things, to get custom validation working with the multiselect drop-down. Full plunk below.

2 thoughts on “Angular Custom Form Validation”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.