I am trying to create a custom validator in model driven form,is this right way to implement it

1.4k views Asked by At

while implementing my code i am getting following errors in console

Error: Cannot find control with name: 'password' at _throwError (forms.js:1732) at setUpControl (forms.js:1640) at FormGroupDirective.push../node_modules/@angular/forms/fesm5/forms.js.FormGroupDirective.addControl (forms.js:4454) at FormControlName.push../node_modules/@angular/forms/fesm5/forms.js.FormControlName._setUpControl (forms.js:4959) at FormControlName.push../node_modules/@angular/forms/fesm5/forms.js.FormControlName.ngOnChanges (forms.js:4909) at checkAndUpdateDirectiveInline (core.js:9244) at checkAndUpdateNodeInline (core.js:10512) at checkAndUpdateNode (core.js:10474) at debugCheckAndUpdateNode (core.js:11107) at debugCheckDirectivesFn (core.js:1106

i tried to create a new form group by grouping password and repassword control,but it does not worked for me.

add-organization.ts

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


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

     myform: FormGroup;
     passwords: FormGroup;
     organizationName: FormControl;
     organizationAddress: FormControl;
     pinCode: FormControl;
     mobileNumber: FormControl;
     organizationType: string[] = ["WholeSale","Retail"];
     businessType: FormControl;
     ownerName: FormControl;
     password: FormControl;
     rePassword: FormControl;
     telephoneNumber: FormControl;
     gstin: FormControl;




  createFormControls() {

     this.organizationName = new FormControl("", Validators.required);
     this.ownerName = new FormControl("", Validators.required);
     this.organizationAddress = new FormControl("", Validators.required);
     this.pinCode = new FormControl("", Validators.required);
     this.mobileNumber = new FormControl("", Validators.required);
     this.telephoneNumber = new FormControl();
     this.businessType = new FormControl("", Validators.required);
     this.gstin = new FormControl("", [Validators.required]);
     this.passwords = new FormGroup({
      password: this.password = new FormControl("",[Validators.required,Validators.minLength(8)]),
      repassword: this.rePassword = new FormControl("",[Validators.required])
      },{ validators: this.passwordValidator }
      )
  }

  passwordValidator(fb: FormGroup) {
    let password  = fb.controls.password.value;
    let repass = fb.controls.repassword.value; 
      if (repass !== password) {
        return {
          passwordMatch: {
            passwordMatch: password
          }
        }
      }
    return null;
  } 

  createForm() {
    this.myform = new FormGroup({
    ownerName: this.ownerName,
    organizationName: this.organizationName,
    organizationAddress: this.organizationAddress,
    pinCode: this.pinCode,
    mobileNumber: this.mobileNumber,
    telephoneNumber: this.telephoneNumber,
    businessType: this.businessType,
    gstin: this.gstin,
    }); 
  }

  onSubmit() {
    if (this.myform.valid) {
      console.log("Form Submitted!");
      console.log(this.myform.value);
      this.myform.reset();
    }
  }


  constructor() { }

  ngOnInit() {
    this.createFormControls();
    this.createForm();
  }

}

add-organization.html

    <mat-form-field>
            <input matInput placeholder="Set password" type = "password" formControlName="password">
            <mat-error *ngIf="password.errors?.required">Password is required</mat-error>
            <mat-error *ngIf="password.errors?.minlength">
                Password must be {{password.errors.minlength.requiredLength}} characters long, we need another {{password.errors.minlength.requiredLength - password.errors.minlength.actualLength}} characters
            </mat-error>
          </mat-form-field>
          <mat-form-field>
            <input  matInput placeholder="Re-Enter password" type = "password" formControlName="rePassword">
            <mat-error *ngIf="rePassword.errors?.required">Password is required</mat-error>
            <mat-error *ngIf="passwords.validators.passwordValidator">not match</mat-error>
   </mat-form-field>

i expect the out to show error message if both password and rePassword are not same else not

3

There are 3 answers

10
bryan60 On

this is how I would do this:

function passwordValidator() { // wrapper fn
  return (ctrl: AbstractControl) => { // return the function w abstractControl
    const fb = ctrl as FormGroup; //type it
    const passwordCtrl  = fb.get('password'); // get the ctrls
    const repassCtrl = fb.get('repassword'); 
    if (!passwordCtrl || !repassCtrl) // handle errors!
      throw new Error('need password and repass controls');
    if (passwordCtrl.value // make sure forms have values (required error should show in this case, not pass match)
          && repassCtrl.value 
          && repassCtrl.value !== passwordCtrl.value) {
      return {
        passwordMatch: password //redunant
      }
    }
    return null;
  }
}

define that outside of your component (so it's reusable) then use like:

{ validators: [passwordValidator()] }

however, your error is really here when you build your group:

this.passwords = new FormGroup({
  password: new FormControl("",[Validators.required,Validators.minLength(8)]),
  repassword: new FormControl("",[Validators.required])
  },{ validators: [passwordValidator()] }
)

you just can't build forms the way you were doing it. you should define your password and repassword variables more like this:

get password() { return this.passwords.get('password');
get repassword() { return this.passwords.get('repassword');

to get the error to appear as you have it (inside a mat error field) then you need to add this weird thing to your component:

parentErrorMatcher = {
  isErrorState(control: FormControl): boolean {
    return control.parent.invalid && control.touched;
  }
};

then use it in template on the form field where you want to display the error:

<mat-form-field [errorStateMatcher]="parentErrorMatcher">

this will cause the error state to reflect the parent instead of the child

1
Sachin Gupta On

You have created the Form twice. Once, with all the controls in createFormControls(). Another in createForm(). THe createForm does not have the password field, thus it does not create a password control, hence the html throws error while creating a formControl with the name password.

What you need in the createForm() is patchValue() to update the value of the controls without changing the structure of the form.

1
Eliseo On

it's only and advise. When you create a formGroup you can use directly

this.myform = new FormGroup({
    ownerName: new FormControl('',Validators.Required),
    organizationName: new FormControl('',Validators.Required),
    organizationAddress: new FormControl('',Validators.Required),
    pinCode: new FormControl('',Validators.Required),
    mobileNumber: new FormControl('',Validators.Required),
    telephoneNumber: new FormControl(''),
    businessType: new FormControl('',Validators.Required),
    gstin: new FormControl('',Validators.Required),
    password:new FormGroup({
      password: new FormControl("",[Validators.required,Validators.minLength(8)]),
      repassword: this.rePassword = new FormControl("",[Validators.required])
      },{ validators: this.passwordValidator }
      )
    }); 

If you use {validators:this.passwordValidator} your function is declared as

passwordValidator(formGroup:FromGroup)
{ 
     ....
}

If you use { validators: this.passwordValidator() } //<--see the ()

passwordValidator()
{
  return (formGroup:FromGroup)=>
  { 
     ....
  }
}