How to display an error message based on custom validation rules in Angular 2?

I use a template-based approach to build forms in Angular 2, and I have successfully created custom validators that I can use in a template.

However, I cannot find a way to display a specific error message associated with specific errors. I want to distinguish why the form is invalid. How to do it?

import { Component } from '@angular/core'; import { NgForm } from '@angular/forms'; import { Site } from './../site'; import { BackendService } from './../backend.service'; import { email } from './../validators'; import { CustomValidators } from './../validators'; @Component({ templateUrl: 'app/templates/form.component.html', styleUrls: ['app/css/form.css'], directives: [CustomValidators.Email, CustomValidators.Url, CustomValidators.Goof], providers: [BackendService] }) export class FormComponent { active = true; submitted = false; model = new Site(); onSubmit() { this.submitted = true; console.log(this.model); } resetForm() { this.model = new Site(); this.submitted = false; this.active = false; setTimeout(() => this.active = true, 0); } get diagnostics() { return JSON.stringify(this.model) } } import { Directive, forwardRef } from '@angular/core'; import { NG_VALIDATORS, FormControl } from '@angular/forms'; import { BackendService } from './backend.service'; function validateEmailFactory(backend:BackendService) { return (c:FormControl) => { let EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-] +@ [a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; return EMAIL_REGEXP.test(c.value) ? null : { validateEmail: { valid: false } }; }; } export module CustomValidators { @Directive({ selector: '[email][ngModel],[email][formControl]', providers: [ {provide: NG_VALIDATORS, useExisting: forwardRef(() => CustomValidators.Email), multi: true} ] }) export class Email { validator:Function; constructor(backend:BackendService) { this.validator = validateEmailFactory(backend); } validate(c:FormControl) { return this.validator(c); } } @Directive({ selector: '[url][ngModel],[url][formControl]', providers: [ {provide: NG_VALIDATORS, useExisting: forwardRef(() => CustomValidators.Url), multi: true} ] }) export class Url { validator:Function; constructor(backend:BackendService) { this.validator = validateEmailFactory(backend); } validate(c:FormControl) { var pattern = /(https?:\/\/)?(www\.)?[ -a-zA-Z0-9@ :%._\+~#=]{2,256}\.[az]{2,4}\b([ -a-zA-Z0-9@ :%_\+.~#?&//=]*)/; return pattern.test(c.value) ? null : { validateEmail: { valid: false } }; } } @Directive({ selector: '[goof][ngModel],[goof][formControl]', providers: [ {provide: NG_VALIDATORS, useExisting: forwardRef(() => CustomValidators.Goof), multi: true} ] }) export class Goof { validate(c:FormControl) { return { validateGoof: { valid: false } }; } } } 
+7
angular typescript angular2-template angular2-forms angular2-directives
source share
3 answers

You can simply check the AbstractControl#hasError(...) method to see if the control has a specific error. FormGroup and FormControl are equal to AbstractControl s. for FormControl you simply pass the name of the error as an argument. for example

 function regexValidator(control: FormControl): {[key:string]: boolean} { if (!control.value.match(/^pee/)) { return { 'badName': true }; } } <div *ngIf="!nameCtrl.valid && nameCtrl.hasError('badName')" class="error">Name must start with <tt>pee</tt>. </div> 

The validator method should return a string / boolean map, where the key is the name of the error. This is the name you are checking in the hasError method.

For FormGroup you can pass the path to FormControl as an additional parameter.

 <div *ngIf="!form.valid && form.hasError('required', ['name'])" class="error">Form name is required.</div> 

name is simply the identifier of the FormControl for input.

Here is an example with validating FormControl and FormGroup .

 import { Component } from '@angular/core'; import { FormGroup, FormBuilder, FormControl, Validators, AbstractControl, REACTIVE_FORM_DIRECTIVES } from '@angular/forms'; function regexValidator(control: FormControl): {[key:string]: boolean} { if (!control.value.match(/^pee/)) { return { 'badName': true }; } } @Component({ selector: 'validation-errors-demo', template: ` <div> <h2>Differentiate Validation Errors</h2> <h4>Type in "peeskillet"</h4> <form [formGroup]="form"> <label for="name">Name: </label> <input type="text" [formControl]="nameCtrl"/> <div *ngIf="!nameCtrl.valid && nameCtrl.hasError('required')" class="error">Name is required.</div> <div *ngIf="!nameCtrl.valid && nameCtrl.hasError('badName')" class="error">Name must start with <tt>pee</tt>.</div> <div *ngIf="!form.valid && form.hasError('required', ['name'])" class="error">Form name is required.</div> </form> </div> `, styles: [` .error { border-radius: 3px; border: 1px solid #AB4F5B; color: #AB4F5B; background-color: #F7CBD1; margin: 5px; padding: 10px; } `], directives: [REACTIVE_FORM_DIRECTIVES], providers: [FormBuilder] }) export class ValidationErrorsDemoComponent { form: FormGroup; nameCtrl: AbstractControl; constructor(formBuilder: FormBuilder) { let name = new FormControl('', Validators.compose([ Validators.required, regexValidator ])); this.form = formBuilder.group({ name: name }); this.nameCtrl = this.form.controls['name']; } } 

UPDATE

Ok, so I got it, but it's a little detailed. I could not figure out how to properly access individual FormControl inputs. So what I did, just created a link to ForgGroup

 <form #f="ngForm" novalidate> 

Then, in order to verify the validity, I simply use the hasError overload, which went the way to the form control name. For <input> , which use name and ngModel , the value name added to the main FormGroup with this name as the name of FormControl . This way you can access it, for example

 `f.form.hasError('require', ['nameCtrl'])` 

subject to name=nameCtrl . Pay attention to f.form . f is an instance of NgForm that has a FormGroup form member FormGroup .

Below is a refactored example

 import { Component, Directive } from '@angular/core'; import { FormControl, Validators, AbstractControl, NG_VALIDATORS, REACTIVE_FORM_DIRECTIVES } from '@angular/forms'; function validateRegex(control: FormControl): {[key:string]: boolean} { if (!control.value || !control.value.match(/^pee/)) { return { 'badName': true }; } } @Directive({ selector: '[validateRegex]', providers: [ { provide: NG_VALIDATORS, useValue: validateRegex, multi: true } ] }) export class RegexValidator { } @Component({ moduleId: module.id, selector: 'validation-errors-template-demo', template: ` <div> <h2>Differentiate Validation Errors</h2> <h4>Type in "peeskillet"</h4> <form #f="ngForm" novalidate> <label for="name">Name: </label> <input type="text" name="nameCtrl" ngModel validateRegex required /> <div *ngIf="!f.form.valid && f.form.hasError('badName', ['nameCtrl'])" class="error">Name must start with <tt>pee</tt>.</div> <div *ngIf="!f.form.valid && f.form.hasError('required', ['nameCtrl'])" class="error">Name is required.</div> </form> </div> `, styles: [` .error { border-radius: 3px; border: 1px solid #AB4F5B; color: #AB4F5B; background-color: #F7CBD1; margin: 5px; padding: 10px; } `], directives: [REACTIVE_FORM_DIRECTIVES, RegexValidator] }) export class ValidationErrorsTemplateDemoComponent { } 
+11
source share

I wrote a set of directives similar to ng-messages from AngularJs to solve this problem in Angular. https://github.com/DmitryEfimenko/ngx-messages

 <div [val-messages]="myForm.get('email')"> <span val-message="required">Please provide email address</span> <span val-message="server" useErrorValue="true"></span> </div> 
+1
source share

To verify the correctness of the template, use the attribute directive as shown below:

 import { Model } from '../../models'; import { Directive, Attribute, forwardRef, Input } from '@angular/core'; import { NG_VALIDATORS, Validator, AbstractControl } from '@angular/forms'; @Directive({ selector: '[unique-in-list][formControlName],[unique-in-list][formControl],[unique-in-list][ngModel]', providers: [ { provide: NG_VALIDATORS, useExisting: forwardRef(() => UniqueInListDirectiveValidator), multi: true } ] }) export class UniqueInListDirectiveValidator implements Validator { @Input('list') private list: Model[]; @Input('property') private _property: string; @Input('thisid') private _myId: string; validate(control: AbstractControl): { [key: string]: any } { let currValue = control.value; if (currValue && currValue.length > 0 && this.list && Array.isArray(this.list) && this.list.length > 0) { let model = this.list.find( (testModel: Model) => { return testModel.modelId !== this._myId && testModel[this._property] === currValue; } ); if (model) { return { isUnique: true } } } return null; } } 

.. with accompanying markup in the template:

 <input type="text" #nameVar="ngModel" name="name" class="form-control name-field" unique-in-list [list]="models" [thisid]="model.modelId" property="name" [(ngModel)]="model.name" required /> <div *ngIf="nameVar.errors?.required && (submitted || !nameVar.pristine)" class="alert-classes-here"> <div>Name is required</div> </div> <div *ngIf="nameVar.errors?.isUnique && (submitted || !nameVar.pristine)" class="alert-classes-here"> <div>Name is already taken</div> </div> 

NTN

0
source share

All Articles