Search code examples
angulartypescriptmdbootstrap

How can I manually mark an input in my mdbootstrap angular.js form as invalid?


With my current code I am able to make inputs show up as invalid if they are blank or do not fit the format of the input's type (eg: email must have the '@' and '.' format).

My next step is to make sure the password and confirmPassword fields match up. I have created a function that compares the two, but am having a lot of trouble implementing it with the mdbootstrap form.

The function that I have been playing around with:

mustMatch(controlName: string, matchingControlName: string) {
return (formGroup: FormGroup) => {
  const control = formGroup.controls[controlName];
  const matchingControl = formGroup.controls[matchingControlName];

  if (matchingControl.errors && !matchingControl.errors.mustMatch) {
    // var passwordInput = document.getElementById('password');
    // passwordInput.classList.remove('ng-valid');
    // passwordInput.classList.add('ng-invalid');
    formGroup.controls['password'].setErrors({'incorrect': true});
    return;
  }

  if (control.value !== matchingControl.value) {
    matchingControl.setErrors({ mustMatch: true });
  } else {
    matchingControl.setErrors(null);
  }
};

}

The elements I need to make invalid:

<div class="col-xs-12">
      <div class="form-outline">
        <input formControlName="password" type="password" id="password" class="form-control pb-3" required/>
        <label class="form-label" for="password">Password</label>
        <div class="invalid-feedback">Please enter your password.</div>
      </div>
    </div>

    <div class="col-xs-12">
      <div class="form-outline">
        <input formControlName="confirmPassword" type="password" id="confirmPass" class="form-control pb-3" required/>
        <label class="form-label" for="confirmPass">Confirm Password</label>
        <div class="invalid-feedback">Please confirm your password.</div>
      </div>
    </div>

The initialization of the form:

ngOnInit(): void {
document.querySelectorAll('.form-outline').forEach((formOutline) => {
  new mdb.Input(formOutline).init();
});
this.setupSignupForm();

const forms = document.querySelectorAll('.needs-validation');

Array.prototype.slice.call(forms).forEach((form) => {
  form.addEventListener('submit', (event) => {
    if (!form.checkValidity()) {
      event.preventDefault();
      event.stopPropagation();
    }
    form.classList.add('was-validated');
  }, false);
});



setupSignupForm(): void {
    this.signupForm = this.formBuilder.group({
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      email: ['',
        [
          Validators.required,
          Validators.email,
        ]],
      confirmEmail: ['',
        [
          Validators.required,
          Validators.email,
        ]],
      joinCode: ['', Validators.required],
      password: ['', Validators.required],
      confirmPassword: ['',
        [
          Validators.required,
        ]]
    }, {
      validators: [this.mustMatch('password', 'confirmPassword'), this.mustMatch('email', 'confirmEmail')]
    });
  }

Please let me know if you can figure out how to do this. I have been bashing my head against the wall for a while on this problem!

Here is a picture of what I am dealing with:


Solution

  • I think it's better to use the validator for each form-control you need instead of the whole form-group, because:

    • The form-group validators will be checked on any changes within the form, even if the changes are not related to the target form-control(s) (password and email in your case).
    • You have to handle setting the errors (setErrors) manually, and the same form removing them.

    Instead of that you can achieve it by assigning the validator to the form-control itself, like the following:

    setupSignupForm(): void {
      this.signupForm = this.formBuilder.group({
        firstName: ['', Validators.required],
        lastName: ['', Validators.required],
        email: ['', [Validators.required, Validators.email]],
        confirmEmail: [
          '',
          [Validators.required, Validators.email, this.mustMatch('email')]
        ],
        joinCode: ['', Validators.required],
        password: ['', Validators.required],
        confirmPassword: ['', [Validators.required, this.mustMatch('password')]]
      });
    }
    
    mustMatch(matchingControlName: string): ValidatorFn {
      return (control: AbstractControl): ValidationErrors | null => {
        if (!control.parent) return;
    
        const matchingCtrlValue = control.parent.get(matchingControlName).value;
    
        if (control.value !== matchingCtrlValue) {
          return { mustMatch: true };
        } else {
          return null;
        }
      };
    }