Page 18 - MSDN Magazine, March 2018
P. 18

anything else—out of the box, Angular doesn’t provide any built-in UI to indicate that the form is invalid. It’s up to the developer to signal to the user in some way that the field requires attention. This can be done in a variety of ways, all dependent on what the devel- oper is using for UI support. For example, it’s common when using the Bootstrap CSS framework to flag the form field as requiring attention by coloring it (or some portion of it) red. Alternatively, it’s not uncommon to have a hidden text span below or after the field that will display when the constraints are violated in some way, and tie the span’s hidden attribute to the status of the form.
But that raises a subtle point—you would prefer to know which control within the form is invalid, so that you can tie the feedback directly to that control. Fortunately, the NgForm has a controls property, which is an array of NgControl objects, and each con- trol defined within the form (such as firstName and lastName) will have an NgControl instance to represent it. Thus, you can refer- ence those control objects directly within the hidden attribute’s template expression:
FirstName: <input name="firstName" type="text" [(ngModel)]="model.firstName" required>
<span [hidden]="speakerForm.controls.firstName.valid"> Speakers must have a first name</span><br>
LastName: <input name="lastName" type="text" [(ngModel)]="model.lastName" required>
<span [hidden]="speakerForm.controls.firstName.valid"> Speakers must have a first name</span><br>
Candor compels me to admit that this code has a subtle issue— when run, it will yield a few errors at runtime. That’s because during the earliest stages of the component the NgForm hasn’t con- structed the collection of NgControl objects, and so the expression, speakerForm.controls.firstName, will be undefined.
The easy way to avoid this problem is to define a local template variable for the control, rather than go through the form’s controls array, and use *ngIf directives to test to see if the form is touched or dirty, and if so, whether it’s valid:
FirstName: <input name="firstName" type="text" [(ngModel)]="model.firstName" #firstName="ngModel" required>
<div *ngIf="firstName.invalid &&
(firstName.dirty || firstName.touched)">
<div *ngIf="firstName.errors.required"> A first name is required.
</div> </div>
Essentially, this eliminates the need to work through the speakerForm, but it’s useful to know that the speakerForm object is accessible to us at runtime, albeit with some caveats.
Custom Validation
In those situations where the HTML5 standard doesn’t define a validation you want or need, Angular permits you to write a cus- tom validator that can be invoked to test the field in question. For example, many years ago, let’s assume I had a bad experience with a speaker named Josh. I don’t like Josh. Never did. I don’t care to let the guy be a part of our database, so I want a custom form validator that disallows particular input. (Obviously, this is a pretty pedantic example, but the concepts here hold for just about any kind of validator that could be imagined.)
Like the other validation, Angular wants to try and “tap in” to the HTML syntax as much as possible, which means that even custom
validators will appear like HTML5 validators, so the forbidden- Name validator should appear like any other HTML validation rule:
FirstName: <input name="firstName" type="text" [(ngModel)]="model.firstName" #firstName="ngModel" required forbiddenName="josh">
Within the template, you can simply add the necessary *ngIf directive to test whether the form contains the forbidden name specified, and if so, display a specific error message:
<div *ngIf="firstName.invalid &&
(firstName.dirty || firstName.touched)">
<div *ngIf="firstName.errors.required"> A first name is required.
</div>
<div *ngIf="firstName.errors.forbiddenName">
NO JOSH! </div>
</div>
In those situations where the HTML5 standard doesn’t define a validation you want or need, Angular permits you to write a custom validator that can be invoked to test the field in question.
There. That should keep out any unwanted speakers (looking at you, Josh).
Custom Validator Directives
In order to make this work, Angular requires us to write the val- idator as an Angular directive, which is a means for hooking up some Angular code to an HTML-looking syntactic element, such as the forbiddenName directive in the input field, or the required or even *ngIf. Directives are quite powerful, and quite beyond the room I have here to explore fully. But I can at least explain how validators work, so let’s start by creating a new directive using “ng generate directive ForbiddenValidator” at the command line, and have it scaffold out forbidden-validator.directive.ts:
import { Directive } from '@angular/core';
@Directive({
selector: '[appForbiddenValidator]'
})
export class ForbiddenValidatorDirective {
constructor() { }
}
The selector in the @Directive is the syntax that you want to use in the HTML templates and, frankly, appForbiddenValidator doesn’t really get the heart racing. It should be something a little more clear in its usage, like forbiddenName. Additionally, the Directive needs to tap into the existing collection of validators— without going into too much detail, the providers parameter to the @Directive contains necessary boilerplate to make the Forbidden- ValidatorDirective available to the larger collection of validators:
14 msdn magazine
The Working Programmer


































































































   16   17   18   19   20