Akash Upadhyay
Akash Upadhyay

Reputation: 51

I want to display one error at a time with reactive forms and angular material

I am using Angular reactive forms and Angular material. My code is working good. But I want to display one error at a time

My .html file code

      <form [formGroup]="accountDetailsForm" novalidate (ngSubmit)="onSubmitAccountDetails(accountDetailsForm.value)">

        <mat-form-field class="full-width">
          <input matInput maxlength="25" placeholder="Username" formControlName="username" required>
          <mat-error *ngFor="let validation of account_validation_messages.username">
            <mat-error class="error-message" *ngIf="accountDetailsForm.get('username').hasError(validation.type) && (accountDetailsForm.get('username').dirty || accountDetailsForm.get('username').touched)">{{validation.message}}</mat-error>
          </mat-error>
        </mat-form-field>

        <mat-form-field class="full-width">
          <input matInput type="email" placeholder="Email" formControlName="email" required>
          <mat-error *ngFor="let validation of account_validation_messages.email">
            <mat-error class="error-message" *ngIf="accountDetailsForm.get('email').hasError(validation.type) && (accountDetailsForm.get('email').dirty || accountDetailsForm.get('email').touched)">{{validation.message}}</mat-error>
          </mat-error>
        </mat-form-field>


        <div formGroupName="matching_passwords">
          <mat-form-field class="full-width">
            <input matInput type="password" placeholder="Password" formControlName="password" required>
            <mat-error *ngFor="let validation of account_validation_messages.password">
              <mat-error class="error-message" *ngIf="accountDetailsForm.get('matching_passwords').get('password').hasError(validation.type) && (accountDetailsForm.get('matching_passwords').get('password').dirty || accountDetailsForm.get('matching_passwords').get('password').touched)">{{validation.message}}</mat-error>
            </mat-error>
          </mat-form-field>

          <mat-form-field class="full-width">
            <input matInput type="password" placeholder="Confirm Password" formControlName="confirm_password"  [errorStateMatcher]="parentErrorStateMatcher" required>
            <mat-error *ngFor="let validation of account_validation_messages.confirm_password">
              <mat-error class="error-message" *ngIf="(accountDetailsForm.get('matching_passwords').get('confirm_password').hasError(validation.type)|| accountDetailsForm.get('matching_passwords').hasError(validation.type)) && (accountDetailsForm.get('matching_passwords').get('confirm_password').dirty || accountDetailsForm.get('matching_passwords').get('confirm_password').touched)">{{validation.message}}</mat-error>
            </mat-error>
          </mat-form-field>

        </div>

        <mat-checkbox formControlName="terms">
          I accept terms and conditions
        </mat-checkbox>
        <mat-error *ngFor="let validation of account_validation_messages.terms">
          <mat-error class="error-message" *ngIf="accountDetailsForm.get('terms').hasError(validation.type) && (accountDetailsForm.get('terms').dirty || accountDetailsForm.get('terms').touched)">{{validation.message}}</mat-error>
        </mat-error>

        <button class="submit-btn" color="primary" mat-raised-button type="submit" [disabled]="!accountDetailsForm.valid">
          Submit
        </button>

      </form>

    

My .ts file code

Here, I'm specifying error messages for error that

account_validation_messages = {
    'username': [
      { type: 'required', message: 'Username is required' },
      { type: 'minlength', message: 'Username must be at least 5 characters long' },
      { type: 'maxlength', message: 'Username cannot be more than 25 characters long' },
      { type: 'pattern', message: 'Your username must contain only numbers and letters' },
      { type: 'validUsername', message: 'Your username has already been taken' }
    ],
    'email': [
      { type: 'required', message: 'Email is required' },
      { type: 'pattern', message: 'Enter a valid email' }
    ],
    'confirm_password': [
      { type: 'required', message: 'Confirm password is required' },
      { type: 'areEqual', message: 'Password mismatch' }
    ],
    'password': [
      { type: 'required', message: 'Password is required' },
      { type: 'minlength', message: 'Password must be at least 5 characters long' },
      { type: 'pattern', message: 'Your password must contain at least one uppercase, one lowercase, and one number' }
    ],
    'terms': [
      { type: 'pattern', message: 'You must accept terms and conditions' }
    ]
  }
  
 

Validators for form

 this.accountDetailsForm = this.fb.group({
      username: new FormControl('', Validators.compose([
       UsernameValidator.validUsername,
       Validators.maxLength(25),
       Validators.minLength(5),
       Validators.pattern('^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$'),
       Validators.required
      ])),
      email: new FormControl('', Validators.compose([
        Validators.required,
        Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')
      ])),
      matching_passwords: this.matching_passwords_group,
      terms: new FormControl(false, Validators.pattern('true'))
    })

All is working fine but problem is that I'm using ngFor to display error but I want to display one error at a time with the help of mat-error.

Upvotes: 3

Views: 3342

Answers (4)

Akinbode A.
Akinbode A.

Reputation: 66

My small solution

yourComponent.ts

showOnlyFirstError(formControl: string, errorKey: string): boolean {
    const allErrors = this.yourForm.get(formControl).errors;

    if (allErrors) {
      const firstError = Object.keys(allErrors)[0];

      if (firstError === errorKey) {
        return true;
      } else {
        return false;
      }
    }
    return false;
  }

yourHtml.html

<mat-form-field class="full-width">
  <input matInput maxlength="25" placeholder="Username" formControlName="username" required>
  <mat-error *ngFor="let validation of account_validation_messages.username">
  <mat-error class="error-message" *ngIf="showOnlyFirstError('username', validation.type) && (accountDetailsForm.get('username').dirty || accountDetailsForm.get('username').touched)">{{validation.message}}</mat-error>
  </mat-error>
</mat-form-field>

I hope this helps someone in 2022.

Upvotes: 0

kirodge
kirodge

Reputation: 688

Use css

First hide all errors

mat-error {
  display: none;
}

Then display only the first error of each form field:

mat-form-field mat-error:first-child {
  display: block;
}

Or alternatively only the first error of the form

form mat-error:first-child {
  display: block;
}

Upvotes: 5

Shashikant Devani
Shashikant Devani

Reputation: 2455

You can use for loop in your ts file to find out errors.

In your .ts File

for (let c in this.account_validation_messages.controls) {
      this.account_validation_messages.controls[c].markAsTouched();
    }

Remove this HTML Code and

<mat-error *ngFor="let validation of account_validation_messages.terms">
          <mat-error class="error-message" *ngIf="accountDetailsForm.get('terms').hasError(validation.type) && (accountDetailsForm.get('terms').dirty || accountDetailsForm.get('terms').touched)">{{validation.message}}</mat-error>
        </mat-error>

Replace with this:

<mat-error class="error-message" *ngIf="accountDetailsForm.get('terms').hasError(validation.type) && (accountDetailsForm.get('terms').dirty || accountDetailsForm.get('terms').touched)">{{validation.message}}</mat-error>

Upvotes: 0

Akj
Akj

Reputation: 7231

REFER DEMO PROJECT FOR BETTER UNDERSTANDING.

control-message.component.ts:

import { OnInit } from '@angular/core';
import { Component, Input } from '@angular/core';
import { FormGroup, FormControl, AbstractControl } from '@angular/forms';

import { CustomValidationService } from '../custom-validation.service'

@Component({
  selector: 'app-control-message',
  templateUrl: './control-message.component.html',
  styleUrls: ['./control-message.component.css']
})
export class ControlMessageComponent implements OnInit {

  ngOnInit() {

  }
  @Input() control: FormControl;

  constructor() { }

    /**
     * This method is use to return validation errors
     */
  get errorMessage() {
    for (let propertyName in this.control.errors) {
      if (this.control.errors.hasOwnProperty(propertyName) && this.control.touched) {
        return CustomValidationService.getValidatorErrorMessage(this.getName(this.control), propertyName, this.control.errors[propertyName]);
      }
      if (this.control.valueChanges) {
        return CustomValidationService.showValidatorErrorMessage(propertyName, this.control.errors[propertyName])
      }
    }
    return null;
  }

    /**
     * This method used to find the control name
     * @param control - AbstractControl
     */
  private getName(control: AbstractControl): string | null {
    let group = <FormGroup>control.parent;

    if (!group) {
      return null;
    }

    let name: string;

    Object.keys(group.controls).forEach(key => {
      let childControl = group.get(key);

      if (childControl !== control) {
        return;
      }
      name = key;
    });

    return name;
  }
}

control-message.component.html:

<mat-error *ngIf="errorMessage !== null">
    {{errorMessage}}
</mat-error>

application-form.html:

<h1>Common Error Message with Custom Validation Reactive Form</h1>

<form [formGroup]="accountDetailsForm" novalidate (ngSubmit)="onSubmitAccountDetails(accountDetailsForm.value)">
    <div>
        <mat-form-field class="full-width">
            <input matInput maxlength="25" placeholder="Username" formControlName="username" required>
        </mat-form-field>
        <app-control-message [control]="accountDetailsForm.controls.username"></app-control-message>
    </div>

    <div>
        <mat-form-field class="full-width">
            <input matInput type="email" placeholder="Email" formControlName="email" required>
        </mat-form-field>
        <app-control-message [control]="accountDetailsForm.controls.email"></app-control-message>
    </div>

    <div>
        <mat-form-field class="full-width">
            <input matInput type="password" placeholder="Password" formControlName="password" required>
        </mat-form-field>
        <app-control-message [control]="accountDetailsForm.controls.password"></app-control-message>
    </div>
    <div>
        <mat-form-field>
            <input matInput type="password" placeholder="Confirm Password" formControlName="confirm_password" required>
        </mat-form-field>

        <app-control-message [control]="accountDetailsForm.controls.confirm_password"></app-control-message>
        <mat-error *ngIf="accountDetailsForm.controls.password.value != accountDetailsForm.controls.confirm_password.value">
            Password & Confrm Password Did not matched.
        </mat-error>
    </div>
    <button class="submit-btn" color="primary" mat-raised-button type="submit" [disabled]="!accountDetailsForm.valid || (accountDetailsForm.controls.confirm_password != accountDetailsForm.controls.password)">
  Submit</button>
</form>

application-form.component.ts:

accountDetailsForm: FormGroup;
  constructor(private fb: FormBuilder) {
  }
  ngOnInit() {
    this.createForm()
  }
  onSubmitAccountDetails(val) {
    console.log(val);
  }

  createForm() {
    this.accountDetailsForm = this.fb.group({
      username: ['', [
        Validators.maxLength(25),
        Validators.minLength(5),
        Validators.pattern('^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$'),
        Validators.required
      ]],
      email: [null, [
        Validators.required,
        Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')
      ]],
      password: [null, Validators.required],
      confirm_password: [null, Validators.required]
    })
  }

Upvotes: 0

Related Questions