Raphael Castro
Raphael Castro

Reputation: 59

How do I add specific validation to reactive forms on angular

I'm trying to render a div based on specific errors, my form works but is buggy and is trowing a type error, "undefined is not an object" , and i don't understand why; can someone help me understand what I'm missing? I've tried simplifying my code by using just the .errors propery, that takes the errors away but its not specific enough to so that it knows what error to render depending on the state of the form, any insight would be super helpful :)

HTML

    <form  [formGroup]="form">
    <div class="spacer">
  <label for="oldPassword">Old Password</label><br /><input
    type="text"
    class="form-control"
    id="oldPassword"
    formControlName="oldPassword"
  />
      <div class="alert alert-danger" *ngIf="form.get('oldPassword').errors.passwordValidator">Duplicate Password</div>
      <div class="alert alert-danger" *ngIf="form.get('oldPassword').touched && form.get('oldPassword').errors.required">Old Password is Required</div>
      <div *ngIf="forms.get('oldPassword').pending">Loading...</div>
</div>
<div class="spacer">
  <label for="newPassword">New Password</label><br /><input
    type="text"
    class="form-control"
    id="newPassword"
    formControlName="newPassword"
  />
  <div *ngIf="form.get('newPassword').touched && newPassword.invalid" class="alert alert-danger">New Password is Required</div>
</div>
<div class="spacer">
  <label for="confirmPassword">Confirm Password</label><br /><input
    type="text"
    class="form-control"
    id="confirmPassword"
    formControlName="confirmPassword"
  />
  <div *ngIf="confirmPassword.touched && confirmPassword.invalid" class="alert alert-danger">Confirm Password is Required</div>
</div>
  <button class="btn btn-primary" (click)="onClick()">Submit!</button>
</form>

{{ oldPassword | json}}

TS:

import { Component } from '@angular/core';
import { FormBuilder, Validators, FormGroup, FormControl,} from '@angular/forms'
import { CustomValidators } from '../passwordValidator';

@Component({
  selector: 'app-form',
  templateUrl: './form.component.html',
  styleUrls: ['./form.component.css']
})
export class FormComponent {
form = new FormGroup({
  oldPassword: new FormControl('',Validators.required, CustomValidators.passwordValidator),
  newPassword: new FormControl('',Validators.required),
  confirmPassword: new FormControl('',Validators.required)

})

onClick() {
  console.log(this.form.get('oldPassword').errors)
}
}


//CustomValidators.passwordValidator

CUSTOM VALIDATOR.TS

import { AbstractControl, ValidationErrors, } from '@angular/forms';


export class CustomValidators {
    static passwordValidator(control: AbstractControl): Promise<ValidationErrors | null> {

        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if (control.value === "123") {
                    resolve({ passwordValidator: true })
                }
                resolve(null)
            }, 2000);
        });


    }
}

Here is the error

Upvotes: 2

Views: 90

Answers (1)

AVJT82
AVJT82

Reputation: 73357

You have some issues...

  • <div *ngIf="forms.get('oldPassword').pending">Loading...</div>, remove the s from forms
  • <div *ngIf="confirmPassword.touched && confirmPassword.invalid" is invalid
  • *ngIf="form.get('newPassword').touched && newPassword.invalid" is invalid
  • Your custom async validator should be placed as the third argument
  • Those undefined issues stem from errors being null

Suggestion: Use FormBuilder instead of calling new FormControl(), but I'll keep my sample with your current code. Also usually we use ngSubmit instead of click event in forms.

So first place the async validator in correct place:

oldPassword: new FormControl(
  "",
  [Validators.required],
  [CustomValidators.passwordValidator]
),

The rest of the errors is related to that errors being null in template if there are no errors... where you are calling such as...

*ngIf="form.get('oldPassword').errors.passwordValidator"

You could solve this by adding the safe navigation operator:

*ngIf="form.get('oldPassword').errors?.passwordValidator"

That will work, but I use the following way, which is in my opinion nicer and more intuitive:

*ngIf="form.hasError('passwordValidator', 'oldPassword')"

So here's a stripped down, shortened version of your code:

<form [formGroup]="form" (ngSubmit)="onClick()">
  <label for="oldPassword">Old Password</label><br />
  <input formControlName="oldPassword" />
  <div *ngIf="form.hasError('passwordValidator', 'oldPassword')">
    Duplicate Password
  </div>
  <div *ngIf="form.get('oldPassword').touched && form.hasError('required', 'oldPassword')">
     Old Password is Required
  </div>
  <div *ngIf="form.get('oldPassword').pending">Loading...</div>
  <button type="submit" [disabled]="!form.valid">Submit!</button>
</form>

STACKBLITZ

Upvotes: 1

Related Questions