10
votes

I want to mention that i am using separate generic component, to display errors, so I am not putting them directly inside of html to avoid repetition, so I can't chain and hardcode conditional directly in template.

I have this field and two validations applied:

this.username = new FormControl('', [ Validators.minLength(5), Validators.required ]); 

How do I show validation error message for each of validations? Let's say I want to show both errors if nothing is in field on submit:
"Minimum length is 5"
"Field is required"

And then if you put something inside it should only show:
"Minimum length is 5"

This is an example but, my real life example is comparing two email fields, if they are the same, so if email is not correct is should show:
"Email is not correct"
"Email is not the same as first email field"

So if email is correct and emails are not the same should only show:
"Email is not the same as first email field"

I have validations working if not passed, but I am not sure how to show separately if one validation passed and another one not, because I have to properly append the real reason why it failed and not put some generic reason for both.

Full example:

Component for error display:

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: 'app-field-error-display',
  templateUrl: './field.error.display.component.html',
  styleUrls: ['./field.error.display.component.css']
})
export class FieldErrorDisplayComponent
{
    @Input() errorMsg: string;
    @Input() displayError: boolean;
}

Component html:

<div *ngIf="displayError" >
  <small>{{errorMsg}}</small>
</div>

Usage of erorr display in Register.html:

<form [formGroup]="_form" (ngSubmit)="register()">
<h4>Create a new account</h4>
<div class="form-group">
  <label for="email">Email Address</label>
  <input class="form-control" name="email" formControlName="email" />
  <app-field-error-display [displayError]="formValidationService.IsFieldValid(_form,'email')" errorMsg="Please insert correct email"></app-field-error-display>
</div>
<div class="form-group">
  <label for="emailConfirm">Email Address Confirm</label>
  <input class="form-control" name="emailConfirm" formControlName="emailConfirm" />
  <app-field-error-display [displayError]="formValidationService.IsFieldValid(_form,'emailConfirm')" errorMsg="Please insert correct email"></app-field-error-display>
</div>
<div class="form-group">
  <label for="password">Password</label>
  <input class="form-control" name="password" formControlName="password" />
  <app-field-error-display [displayError]="formValidationService.IsFieldValid(_form,'password')" errorMsg="Please insert password"></app-field-error-display>
</div>
<button type="submit" class="btn btn-default">Create Account</button>
</form>
<div *ngIf="_registered" class="alert alert-success box-msg" role="alert">
  <strong>Account registered!</strong> You will be redirected in sec
</div>

Register component:

ngOnInit() {
    this._form = this._formBuilder.group({
        email: ['', [Validators.required, Validators.email]],
        emailConfirm: ['', [Validators.required, Validators.email, MatchOtherValidator.Validate('email')]],
        password: ['', [Validators.required]]
    });

    this._registerModel = new RegisterModel();
}

Custom match other validator:

import { FormControl } from '@angular/forms';

export class MatchOtherValidator {
    static Validate(otherControlName: string) {

        let thisControl: FormControl;
        let otherControl: FormControl;

        return function matchOtherValidate(control: FormControl) {

            if (!control.parent) {
                return null;
            }

            // Initializing the validator.
            if (!thisControl) {
                thisControl = control;
                otherControl = control.parent.get(otherControlName) as FormControl;
                if (!otherControl) {
                    throw new Error('matchOtherValidator(): other control is not found in parent group');
                }
                otherControl.valueChanges.subscribe(() => {
                    thisControl.updateValueAndValidity();
                });
            }

            if (!otherControl) {
                return null;
            }

            if (otherControl.value !== thisControl.value) {
                return {
                    matchOther: true
                };
            }

            return null;

        }
    }
}

Form validation service has this method:

public IsFieldValid(form: FormGroup, field: string){
    return !form.get(field).valid && form.get(field).dirty;
}
4
angular.io/guide/form-validation#built-in-validators: *ngIf="username.errors.minlength", *ngIf="username.errors.required". - JB Nizet
can't chain in template directly. - sensei
You put the first condition on the element displaying your min length message, and the second one on the element dispplaying your required message. As shown in the documentation I linked to. What's the concrete problem? Where is your code? - JB Nizet
I added code I hope it makes sense - sensei
It seems you haven't read my comment. Your code only checks if the form control is dirty and invalid. But you want to check if a specific validation fails: return form.get(field).dirty && form.get(field).errors.required to check for the required validation, for example. You can't possibly know, from the invalid boolean, which can only be true or false, which of the validations failed. - JB Nizet

4 Answers

19
votes

Your code only checks if the form control is dirty and invalid. But you want to check if a specific validation fails. So you need to pass an additional error argument ('required', 'minlength', etc.), and use

form.get(field).hasError(error)
15
votes

I am answering this a little late, but I found a solution that is part of the angular materials modules that I really liked. If you implement your fields using "mat-form-field" you can easily display the error messages using a "mat-error" containing spans with an *ngIf.

I figured I should put it out there for other people trying to implement validator error messages. It makes the HTML a little beefy but it looks really nice.

<form [formGroup]="myForm" (ngSubmit)="submitForm()">
        <mat-form-field>
            <input matInput formControlName="user_input">
            <mat-error>
                <span *ngIf="myForm?.controls.user_input?.errors?.required">Required Field</span>
                <span *ngIf="myForm?.controls.user_input?.errors?.minLength">Minimum Length is 5</span>
            </mat-error>
        </mat-form-field>
</form>

The documentation is found here: https://material.angular.io/components/form-field/overview#error-messages

1
votes

if custome validator matchOther fails you should return {matchOther: true} and check form.get(field).hasError('matchOther');

1
votes

In the template you can use

<form class="example-form" [formGroup]="formName" (ngSubmit)="onFormSubmit()">

<input matInput #inputName formControlName="inputName"
              name="inputName" required/>
  <mat-error *ngIf="formName.get('inputName').hasError('required')">
        Email is <strong>required</strong>
  </mat-error>
</form>