2
votes

I am having issues validating my confirm-pass field onBlur in Angular 7. In my case I have built multiple custom validators which work after the control has been made 'dirty', as well as a directive following Pascal Prechts tutorial which DOES work onBlur when field is only touched, but I would like a pure Reactive solution and Pascal's requires adding to the HTML.

Problem Example: Click into firstName field, immediately tab or click out - built-in required validator fires. Click into confirmPass field, immediately click or tab out; custom validator does NOT fire (even console-logs aren't showing). If you were to type any single character prior to tabbing or clicking out however every version I've made does work, at that point.

I have tried building a validator class with multiple static methods. Controls all used one method ('director') which checks the incoming fieldname and redirected appropriately. This returned an error map or null value directly instead of the function, and of course didn't work.

Next I tried Pascal Prechts tutorial; https://blog.thoughtram.io/angular/2016/03/14/custom-validators-in-angular-2.html which worked as a directive - I had to type in equalValidator into the HTML however - which I do not want to do.

Finally I tried the Angular suggested function and another I found on StackOverflow, both of which return a function; which itself returns the error map or null.

The FormGroup

ngOnInit() {
    this.regForm = this.fb.group({
        firstName: ["", [Validators.required, Validators.minLength(2), Validators.maxLength(30)]],
        lastName: ["", [Validators.required, Validators.minLength(2), Validators.maxLength(30)]],
        email: ["", [Validators.required, Validators.email]],
        password: ["", [Validators.required, Validators.minLength(8), Validators.maxLength(55)]],
        confirmPass: ["", matchingInputsValidator], 
    }, { updateOn: 'blur' });
    this.lgnForm = this.fb.group({
        lgnEmail: ["", [Validators.required, Validators.email]],
        lgnPass: ["", Validators.required]
    }, { updateOn: 'blur' });
}

Pascals solution which works onBlur, but as a directive only

function validateEqualFactory(compare: string) : ValidatorFn {
    return (c: FormControl) => {
        // value of confirm password field
        let v = c.value;

        // value of compared field - in this case 'password'
        let e = c.root.get(compare);

        // Ternary operator -> return null if equal or error map if not equal/password does not exist
        return (e && v === e.value) ? null : { misMatch: true };
    };
}

@Directive({
    selector: '[validateEqual][formControl],[validateEqual][formControlName]',
    providers: [
        { provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidator), multi: true }
    ]
})
export class EqualValidator implements Validator {
    validator: ValidatorFn;
    constructor() {
        this.validator = validateEqualFactory("password");
    }
    validate(c: FormControl) {
        return this.validator(c);
    }
}

And finally two of the most recent attempts I have made, the first being a variation of Angular documentations official recommendation

export function confirmPassVal(): ValidatorFn {
    return (control: AbstractControl): {[key: string]: any} | null => {
        console.log("Making it here 1", control);
        const passCtrl = control.root.get('password');
        console.log("Making it here 2: ", passCtrl);
        return (passCtrl && passCtrl.value === control.value) ? null : {"misMatch" : true };
    }
}

export const matchingInputsValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
    let pass;
    control.root.get('password') ? pass = control.root.get('password').value : pass = null;
    let confirmPass = control.value;
    return pass === confirmPass ? null : { misMatch: true };
}

I expect my function to run when the field is touched and/or dirty, but it is currently only running if the field is dirty. In an ideal world I would like to be able to extend the Validators or NG_VALIDATORS class with my own custom functions, purely from the FormBuilder declaration. At this point I believe it has something to do with multi-provider, and extending NG_VALIDATORS but....I'm not sure how to do this even if it is the case.

1
could you create simple stackblitz.com demo that demonstrates the problem?Ludevik
I'll go see what I can do yeahR. Waugh
Here is a stackblitz of the issue: stackblitz.com/edit/angular-crnns8 Notice that on this one the validations are firing before blur for minLength, maxLength, and email....I'm not too worried about this, since it's working fine on the original project. The same issue is replicated in this project however - validations don't occur onBlur for the pristine confirmPass, any help much much appreciatedR. Waugh

1 Answers

3
votes

Angular runs validators first time when FormControl is created and then each time value changes in that FormControl. When you just focus and blur the input there is no revalidation going on.

For required fields it works like this: when you create FormControl trough FormBuilder it gets validated and the error is there from the start. It is just not visible because the input was not touched yet. For matchingInputsValidator the error is not there, because both fields are same at the start, so when you touch the input, no error is shown.

What you need to do is to trigger validation on confirmPass form control on your own whenever password changes. One good place to do that is in valueChanges subscription of password form control.

this.password.valueChanges.subscribe(() => this.confirmPass.updateValueAndValidity());

Here you'll find updated stackblitz demo.