Angular form validation

Posted by philicious on Mon, 16 Mar 2020 12:18:54 +0100

Template driven validation

Using template driven validation depends on Native HTML form validator Angular uses instructions to match these attributes with validation.

There are two kinds of native HTMl validators

  1. Define by semantic type
  2. Define by verifying related properties

semantic type

Input type Constraint description Associated violation
<input type="URL"> The value must be an absolute URL, as defined in the URL Living Standard. TypeMismatch constraint violation
<input type="email"> The value must be a syntactically valid email address, which generally has the format username@hostname.tld. TypeMismatch constraint violation

Verify related properties

Attribute Input types supporting the attribute Possible values Constraint description Associated violation
pattern text, search, url, tel, email, password A JavaScript regular expression (compiled with the ECMAScript 5 global, ignoreCase, and multiline flags disabled) The value must match the pattern. patternMismatch constraint violation
min range, number A valid number The value must be greater than or equal to the value. rangeUnderflow constraint violation
date, month, week A valid date
datetime, datetime-local, time A valid date and time
max range, number A valid number The value must be less than or equal to the value rangeOverflow constraint violation
date, month, week A valid date
datetime, datetime-local, time A valid date and time
required text, search, url, tel, email, password, date, datetime, datetime-local, month, week, time, number, checkbox, radio, file; also on the <select> and <textarea> elements none as it is a Boolean attribute: its presence means true, its absence means false There must be a value (if set). valueMissing constraint violation
step date An integer number of days Unless the step is set to the any literal, the value must be min + an integral multiple of the step. stepMismatch constraint violation
month An integer number of months
week An integer number of weeks
datetime, datetime-local, time An integer number of seconds
range, number An integer
minlength text, search, url, tel, email, password; also on the <textarea> element An integer length The number of characters (code points) must not be less than the value of the attribute, if non-empty. All newlines are normalized to a single character (as opposed to CRLF pairs) for <textarea>. tooShort constraint violation
maxlength text, search, url, tel, email, password; also on the <textarea> element An integer length The number of characters (code points) must not exceed the value of the attribute. tooLong constraint violation

 

Whenever the value in the form control changes, Angular validates and generates a list of validation errors (corresponding to the INVALID state) or null (corresponding to the VALID state);

You can view the status of a control by exporting it as a local template variable, such as the following

<input id="name" name="name" class="form-control" required minlength="4" appForbiddenName="bob"
      [(ngModel)]="hero.name" #name="ngModel" >

<div *ngIf="name.invalid && (name.dirty || name.touched)"
    class="alert alert-danger">

  <div *ngIf="name.errors.required">
    Name is required.
  </div>
  <div *ngIf="name.errors.minlength">
    Name must be at least 4 characters long.
  </div>
  <div *ngIf="name.errors.forbiddenName">
    Name cannot be Bob.
  </div>
</div>

 #name="ngModel "Put NgModel Export to a local variable named name. NgModel Control yourself FormControl The properties of the instance are mapped so that you can check the state of the control in the template, such as valid and dirty. For complete control properties, see the AbstractControl.

Validation of responsive forms

The source of responsive form control is component class, so we can't add validators through the attributes on the template, but directly add validator functions to the form control model in the component class. These functions are called when the control changes.

ngOnInit(): void {
  this.heroForm = new FormGroup({
    'name': new FormControl(this.hero.name, [
      Validators.required,
      Validators.minLength(4),
      forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
    ]),
    'alterEgo': new FormControl(this.hero.alterEgo),
    'power': new FormControl(this.hero.power, Validators.required)
  });

}
<input id="name" class="form-control" formControlName="name" required >

<div *ngIf="name.invalid && (name.dirty || name.touched)"
    class="alert alert-danger">

  <div *ngIf="name.errors.required">
    Name is required.
  </div>
  <div *ngIf="name.errors.minlength">
    Name must be at least 4 characters long.
  </div>
  <div *ngIf="name.errors.forbiddenName">
    Name cannot be Bob.
  </div>
</div>
  • Instead of exporting any instructions, the form uses the name reader defined in the component class.
  • The required property still exists, and although validation no longer requires it, you still need to keep it in the template to support CSS style or accessibility.  

There are two verifier functions in the verification process: synchronous verifier and asynchronous verifier.

  • The synchronous validator function takes a control instance and returns a set of validation errors or null s. It can pass the function as the second parameter of the constructor when instantiating FormControl.
  • The asynchronous validator function also receives a control instance and returns a Promise or Observable object. At last, the function returns a set of validation errors or null s. You can pass FormControl as the third function of the constructor when instantiating it.

For performance reasons, Angular runs asynchronous validators only after all synchronous validators have passed. These validation errors are not set until each asynchronous validator has been executed.

There are also two types of verifiers from the source of verifier: built-in verifier and custom verifier

  • The built-in validator is similar to the validator used in the form. Validators implement the function with the same name, as follows See API for details.
class Validators {
  static min(min: number): ValidatorFn
  static max(max: number): ValidatorFn
  static required(control: AbstractControl): ValidationErrors | null
  static requiredTrue(control: AbstractControl): ValidationErrors | null
  static email(control: AbstractControl): ValidationErrors | null
  static minLength(minLength: number): ValidatorFn
  static maxLength(maxLength: number): ValidatorFn
  static pattern(pattern: string | RegExp): ValidatorFn
  static nullValidator(control: AbstractControl): ValidationErrors | null
  static compose(validators: ValidatorFn[]): ValidatorFn | null
  static composeAsync(validators: AsyncValidatorFn[]): AsyncValidatorFn | null
}  
  • Custom validator: DEMO is as follows. When written together, it can be used for both template and component class.
// Declared as an instruction to use shared / forbidden name. Directive. TS (Directive) for template validation 
@Directive({
  selector: '[appForbiddenName]',
  providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
})
export class ForbiddenValidatorDirective implements Validator {
  @Input('appForbiddenName') forbiddenName: string;

  validate(control: AbstractControl): {[key: string]: any} | null {
    return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control) : null;
  }
}
// Defining functions for verification methods
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? {'forbiddenName': {value: control.value}} : null;
  };
}

This function is actually a factory that accepts a regular expression to detect whether the specified name has been disabled and returns a validator function.

The forbiddenNameValidator factory function returns the configured validator function. This function takes an Angular controller object and returns null when the controller value is valid or a validation error object when it is invalid. The validation error object usually has a property called the validation secret key (Forbidden name). Its value is an arbitrary dictionary that you can use to insert error messages ({name}).

Custom asynchronous validators are similar to synchronous validators, except that they must return a Promise or observable object that will later output null or "validation error object". If it is an observable object, it must be completed at a certain point in time, when the form will use the last value it outputs as the validation result. HTTP service is automatic, but some custom observable objects may need to call the complete method manually

CSS class representing control state

Angular will automatically map many control properties as CSS classes to the elements of the control. You can use these classes to style form control elements based on the state of the form. The following classes are currently supported:

.ng-valid

.ng-invalid

.ng-pending

.ng-pristine

.ng-dirty

.ng-untouched

.ng-touched

.ng-valid[required], .ng-valid.required  {
  border-left: 5px solid #42A948; /* green */
}


.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}

Cross field cross validation

In addition to the individual control verification, sometimes the joint verification of multiple controls is also needed. At this time, cross field verification is needed. First sticky code

  • This authenticator implements ValidatorFn Interface. It takes an Angular form control object as a parameter. When the form is valid, it returns a null, otherwise it returns ValidationErrors Object.
  • Let's call FormGroup Of get Method to get the child control. Then, briefly compare the values of the name and alter ego controls.
// First, define the instructions for template validation, and then call the exported validation method
@Directive({
  selector: '[appIdentityRevealed]',
  providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }]
})
export class IdentityRevealedValidatorDirective implements Validator {
  validate(control: AbstractControl): ValidationErrors {
    return identityRevealedValidator(control)
  }
}
// The following methods can be used for instructions or component classes
export const identityRevealedValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
  const name = control.get('name');
  const alterEgo = control.get('alterEgo');

  return name && alterEgo && name.value === alterEgo.value ? { 'identityRevealed': true } : null;
};

Reactive form validation: a new validator needs to be passed to its second parameter when the FormGroup is created

const heroForm = new FormGroup({
  'name': new FormControl(),
  'alterEgo': new FormControl(),
  'power': new FormControl()
}, { validators: identityRevealedValidator });

Template driven form validation: we want to add this instruction to the HTML template. Since the validator must be registered at the top level of the form, we will place the instruction on the form label.

<form #heroForm="ngForm" appIdentityRevealed>

<!-- Error output-->
<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" 
class="cross-validation-error-message alert alert-danger">
    Name cannot match alter ego.
</div>

Asynchronous verifier

It's like the synchronous verifier has ValidatorFn and Validator Like interface, asynchronous validators have their own corresponding interfaces: AsyncValidatorFn and AsyncValidator.

They are very similar, but have the following differences:

  • They must return Promise or Observable,

  • The observable object returned must be limited, that is, it must be complete d at some point in time. To transform an infinite observable object into a finite one, you can use filter pipes such as first, last, take, or takeUntil to process it.

Be careful! Asynchronous verification is always performed after synchronous verification, and only after synchronous verification is successful. If a more basic validation method has failed, this can help the form avoid potentially expensive asynchronous validation processes, such as HTTP requests.

After the asynchronous validator starts, the form control enters the pending state. You can monitor the control's pending property and use it to give users some visual feedback that validation is in progress.

A common UI processing mode is to display a spinner when performing asynchronous validation. The following example shows what to do in a template driven form:

<input [(ngModel)]="name" #model="ngModel" appSomeAsyncValidator>
<app-spinner *ngIf="model.pending"></app-spinner>

Implement custom validators

@Injectable({ providedIn: 'root' })
export class UniqueAlterEgoValidator implements AsyncValidator {
  constructor(private heroesService: HeroesService) {}

  validate(
    ctrl: AbstractControl
  ): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
    return this.heroesService.isAlterEgoTaken(ctrl.value).pipe(
      map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null)),
      catchError(() => of(null))
    );
  }
}
// HeroesService is responsible for sending an HTTP request to hero database
interface HeroesService {
  isAlterEgoTaken: (alterEgo: string) => Observable<boolean>;
}

When validation begins, UniqueAlterEgoValidator delegates the task to the isAlterEgoTaken() method of the HeroesService and passes in the value of the current control. At this point, the control is marked as pending until the observable object returned by the validate() method is complete.

The isAlterEgoTaken() method will issue an HTTP request and return an observable < Boolean > result. We string the response object through the map operator and turn it into a valid result. As usual, returns null if the form is valid, otherwise returns ValidationErrors . We still use the catch error operator to ensure that any potential errors are handled.

Here, we decided to treat the error in isAlterEgoTaken() as a successful validation. You can also treat it as a failure and return the ValidationError object.

After a period of time, the observable object is finished, and the asynchronous verification is finished. At this time, the pending flag is changed to false, and the validity of the form is updated.

Performance considerations

By default, all validators are executed whenever the form value changes. For the synchronous verifier, there is no significant impact on application performance. However, asynchronous validators typically perform some kind of HTTP request to validate the control. If sending HTTP request after each key press will bring heavy burden to back-end API, it should be avoided as much as possible.

We can change the updateOn property from change (the default) to submit or blur to delay the update of form validation.

For template driven forms:

<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">

For responsive forms:

new FormControl('', {updateOn: 'blur'});

 

Published 2 original articles, praised 0 and visited 8
Private letter follow

Topics: angular Attribute less Javascript