We often are going to want to write our own custom validations. To see how validators are implemented, let’s look at Validators.required from the Angular core source:
export class Validators { static required(c: FormControl): StringMap<string, boolean> { return isBlank(c.value) || c.value == "" ? {"required": true} : null; }
A validator: – Takes a FormControl as its input and – Returns a StringMap<string, boolean> where the key is “error code” and the value is true if it fails
Writing the Validator
Let’s say we have specific requirements for our sku. For example, say our sku needs to begin with 123. We could write a validator like so:
function skuValidator(control: FormControl): { [s: string]: boolean } { if (!control.value.match(/^123/)) { return {invalidSku: true}; } }
This validator will return an error code invalidSku if the input (the control.value) does not begin with 123.
Assigning the Validator to the FormControl
Now we need to add the validator to our FormControl. For that, we use Validators.compose:
constructor(fb: FormBuilder) { this.myForm = fb.group({ 'sku': ['', Validators.compose([ Validators.required, skuValidator])] });
Validators.compose wraps our two validators and lets us assign them both to the FormControl. The FormControl is not valid unless both validations are valid. Now we can use our new validator in the view:
<div *ngIf="sku.hasError('invalidSku')" class="ui error message">SKU must begin with <span>123</span></div>
ngModel
NgModel is a special directive: it binds a model to a form. ngModel is special in that it implements two-way data binding. Two-way data binding is almost always more complicated and difficult to reason about vs. one-way data binding. Angular is built to generally have data flow one-way: topdown. However, when it comes to forms, there are times where it is easier to opt-in to a two-way bind.
Example:
export class DemoFormNgModelComponent { myForm: FormGroup; productName: string; constructor(fb: FormBuilder) { this.myForm = fb.group({ 'productName': ['', Validators.required] }); } onSubmit(value: string): void { console.log('you submitted value: ', value); } }
Note: We’re simply storing productName: string as an instance variable. Next, let’s use ngModel on our input tag:
<label for="productNameInput">Product Name</label> <input type="text" id="productNameInput" placeholder="Product Name" [formControl]="myForm.get('productName')" [(ngModel)]="productName">
The syntax for ngModel is something like funny we are using both brackets and parentheses around the ngModel attribute! The idea this is intended to invoke is that we’re using both the input [] brackets and the output () parentheses. It’s an indication of the two-way bind. Notice something else here: we’re still using formControl to specify that this input should be bound to the FormControl on our form. We do this because ngModel is only binding the input to the instance variable – the FormControl is completely separate. But because we still want to validate this value and submit it as part of the form, we keep the formControl directive.
If you have any query regarding Custom Validations in Angular Forms, drop a comment below.