Search code examples
angularvalidationcustomvalidator

Code jumps straight into validator and breaks everything


I've writtten my own validators to check the input fields of my reactive form but they don't seem to work. The first two don't ever produce an error and the last one breaks everything once i add it to my code. With augury I've been debugging the best I can and I found out that with my validatePasswords, he immediately jumps into the method whilst I havn't even touched anything. If any of you can figure out why it does this or why my other validators don't ever produce an error, that would be amazing!

My 3 custom validators

function validateEmail(control: FormGroup): { [key: string]: any } {
  var mailReg = /^([a-zA-Z]+[a-zA-Z0-9.\-_éèàùäëïöüâêîôû]*)@([a-z]+)[.]([a-z]+)([.][a-z]+)*$/g;
  if (!mailReg.test(control.get('email').value)) {
    return { noValidEmail: true };
  }
  return null;
}
function validatePhone(control: FormGroup): { [key: string]: any } {
  var phoneReg = /^0{0,2}(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\d{7,14}$/;
  if (!phoneReg.test(control.get('phone').value)) {
    return { noValidPhone: true };
  }
  return null;
}

function validatePassword(control: FormGroup): { [key: string]: any } {
  if (
    control.get('password').value !== control.get('passwordConfirmation').value
  ) {
    return { noMatchingPasswords: true };
  }
  return null;
}

my constructor and form:

 public register: FormGroup;
  constructor(
    public dialogRef: MatDialogRef<RegisterFormComponent>,
    private fb: FormBuilder,
    private http: HttpClient
  ) {}

  ngOnInit() {
    this.register = this.fb.group({
      firstName: ['', [Validators.required, Validators.minLength(2)]],
      lastName: ['', [Validators.required, Validators.minLength(2)]],
      email: [
        '',
        [Validators.required, Validators.minLength(5)],
        validateEmail
      ],
      phone: [
        '',
        [Validators.required, Validators.minLength(8)],
        validatePhone
      ],
      country: [''],
      password: ['', [Validators.required, Validators.minLength(5)]],
      passwordConfirmation: [
        '',
        [Validators.required, Validators.minLength(5), validatePassword]
      ]
    });
  }

my error message method

  getErrorMessage(errors: any) {
    if (errors.required) {
      return 'is required';
    } else if (errors.noValidEmail) {
      return 'not a valid email';
    } else if (errors.noValidPhone) {
      return 'no valid phone number';
    } else if (errors.noMatchingPasswords) {
      return 'passwords don\'t match';
    } else if (errors.minlength) {
      return `need at least ${errors.minlength.requiredLength} characters`;
    }
  }

the error it produces:

ERROR TypeError: Cannot read property 'get' of undefined
    at FormGroupDirective.push../node_modules/@angular/forms/fesm5/forms.js.FormGroupDirective.addControl (forms.js:5280)
    at FormControlName.push../node_modules/@angular/forms/fesm5/forms.js.FormControlName._setUpControl (forms.js:5882)
    at FormControlName.push../node_modules/@angular/forms/fesm5/forms.js.FormControlName.ngOnChanges (forms.js:5803)
    at checkAndUpdateDirectiveInline (core.js:22085)
    at checkAndUpdateNodeInline (core.js:23353)
    at checkAndUpdateNode (core.js:23315)
    at debugCheckAndUpdateNode (core.js:23949)
    at debugCheckDirectivesFn (core.js:23909)
    at Object.eval [as updateDirectives] (RegisterFormComponent.html:5)
    at Object.debugUpdateDirectives [as updateDirectives] (core.js:23901)

lastly, my form in html

<h3 mat-dialog-title>Register</h3>
<mat-dialog-content>
  <form [formGroup]="register" (ngSubmit)="onSubmit()">
    <mat-form-field>
      <input matInput aria-label="First name" placeholder="First name" type="text" formControlName="firstName"
        class="browser-default" required />
      <mat-error *ngIf="register.get('firstName')['errors'] && register.get('firstName').touched">
        {{ getErrorMessage(register.get('firstName')['errors']) }}
      </mat-error>
    </mat-form-field>
    <mat-form-field>
      <input matInput aria-label="Last name" placeholder="Last name" type="text" formControlName="lastName"
        class="browser-default" required />
      <mat-error *ngIf="register.get('lastName')['errors'] && register.get('lastName').touched">
        {{ getErrorMessage(register.get('lastName')['errors']) }}
      </mat-error>
    </mat-form-field>
    <mat-form-field>
      <input matInput aria-label="Email" placeholder="Email" type="text" formControlName="email" class="browser-default"
        required />
      <mat-error *ngIf="register.get('email')['errors'] && register.get('email').touched">
        {{ getErrorMessage(register.get('email')['errors']) }}
      </mat-error>
    </mat-form-field>
    <mat-form-field>
      <input matInput aria-label="Phone" placeholder="Phone" type="text" formControlName="phone" class="browser-default"
        required />
      <mat-error *ngIf="register.get('phone')['errors'] && register.get('phone').touched">
        {{ getErrorMessage(register.get('phone')['errors']) }}
      </mat-error>
    </mat-form-field>
    <mat-form-field>
      <input matInput aria-label="Country" placeholder="Country" type="text" formControlName="country"
        class="browser-default" />
    </mat-form-field>
    <mat-form-field>
      <input matInput aria-label="Password" placeholder="Password" type="password" formControlName="password"
        class="browser-default" required />
      <mat-error *ngIf="register.get('password')['errors'] && register.get('password').touched">
        {{ getErrorMessage(register.get('password')['errors']) }}
      </mat-error>
    </mat-form-field>
    <mat-form-field>
      <input matInput aria-label="Confirmation Password" placeholder="Confirmation Password" type="password"
        formControlName="passwordConfirmation" class="browser-default" required />
      <mat-error *ngIf="register.get('passwordConfirmation')['errors'] && register.get('passwordConfirmation').touched">
        {{ getErrorMessage(register.get('passwordConfirmation')['errors']) }}
      </mat-error>
    </mat-form-field>
    <div class="buttonDiv">
      <button mat-button (click)="onNoClick()">Cancel</button>
      <button mat-button type="submit" [disabled]="!register.valid">Register</button>
    </div>
  </form>
</mat-dialog-content>

Update:

these errors are new but get now thrown as well:

ERROR Error: formGroup expects a FormGroup instance. Please pass one in.

       Example:


    <div [formGroup]="myGroup">
      <input formControlName="firstName">
    </div>

    In your class:

    this.myGroup = new FormGroup({
       firstName: new FormControl()
    });
    at Function.push../node_modules/@angular/forms/fesm5/forms.js.ReactiveErrors.missingFormException (forms.js:1443)
    at FormGroupDirective.push../node_modules/@angular/forms/fesm5/forms.js.FormGroupDirective._checkFormPresent (forms.js:5414)
    at FormGroupDirective.push../node_modules/@angular/forms/fesm5/forms.js.FormGroupDirective.ngOnChanges (forms.js:5237)
    at checkAndUpdateDirectiveInline (core.js:22085)
    at checkAndUpdateNodeInline (core.js:23353)
    at checkAndUpdateNode (core.js:23315)
    at debugCheckAndUpdateNode (core.js:23949)
    at debugCheckDirectivesFn (core.js:23909)
    at Object.eval [as updateDirectives] (RegisterFormComponent.html:3)
    at Object.debugUpdateDirectives [as updateDirectives] (core.js:23901)

and

ERROR TypeError: Cannot read property 'value' of null
    at validatePassword (register-form.component.ts:29)
    at forms.js:658
    at Array.map (<anonymous>)
    at _executeValidators (forms.js:658)
    at FormControl.validator (forms.js:623)
    at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.AbstractControl._runValidator (forms.js:2914)
    at FormControl.push../node_modules/@angular/forms/fesm5/forms.js.AbstractControl.updateValueAndValidity (forms.js:2890)
    at new FormControl (forms.js:3241)
    at FormBuilder.push../node_modules/@angular/forms/fesm5/forms.js.FormBuilder.control (forms.js:6462)
    at FormBuilder.push../node_modules/@angular/forms/fesm5/forms.js.FormBuilder._createControl (forms.js:6502)

(line 29 is this: control.get('password').value !== control.get('passwordConfirmation').value)


Solution

  • Given that the validatePassword custom validator involves cross-field validation (validation across multiple fields - FormControls password and passwordConfirmation), you should be passing that validator in the second argument of the initialisation of the register form.

    You can keep the other FormControls as they are. Remove validatePassword from passwordConfirmation, and add it to the second argument of the formBuilder.

    this.register = this.fb.group({
      .
      .
      password: ['', [Validators.required, Validators.minLength(5)]],
      passwordConfirmation: [
        '',
        [Validators.required, Validators.minLength(5)]
      ]
    }, { validators: validatePassword });
    

    As for your second issue, it is because the register FormGroup is undefined when the page is rendered. You should move it to your constructor, or simply initialise the FormControls when you declare the 'register' property on your class.

    constructor(
        public dialogRef: MatDialogRef<RegisterFormComponent>,
        private fb: FormBuilder,
        private http: HttpClient
      ) {
        this.register = fb.group({ 
          .
          .
          // your FormControls
          .
        });
      }
    

    Or

    export class YourComponent implements OnInit {
    
      public register: FormGroup = this.fb.group({ 
       .
       .
       // your FormControls
       .
      })