Angular (4): Custom Validator in a Template-Driven form shows the current value of the field behind

1.6k views Asked by At

I can't seem to figure out why my custom validator is one step behind of the field value. Example: my input field has value 123 typed one by one. But my validator has value 12.

I can't correctly compare the values between two fields. This is the validator in a directive:

@Directive({
  selector: '[validateEqual][formControlName],[validateEqual][formControl],[validateEqual][ngModel]',
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => PasswordValidationDirective),
      multi: true
    }
  ]
})
export class PasswordValidationDirective implements Validator {
  @Input('first') first: string;
  @Input('second') second: string;

  constructor() {
  }

  public validate(ac: AbstractControl): { [key: string]: any } {
    console.log(ac.root);
    return null;
  }
}

This is the html of the field:

<md-input-container class="full-width">
  <input mdInput
         type="password"
         required
         ngModel name="passwordConfirmation"
         #passwordConfirmation="ngModel"
         minlength="6"
         maxlength="30"
         pattern="(?=^.{6,30}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$"
         validateEqual
         first="password"
         second="passwordConfirmation"
         placeholder="{{'PASSWORD_RECOVERY.PASSWORD_CONFIRMATION' | translate}}">
  <md-error *ngIf="passwordConfirmation.touched && passwordConfirmation.invalid">
    <span *ngIf="passwordConfirmation.errors.required">
      {{'PASSWORD_RECOVERY.FIELD_REQUIRED' | translate}}
    </span>
    <span *ngIf="passwordConfirmation.errors.minlength || passwordConfirmation.errors.maxlength">
      {{'PASSWORD_RECOVERY.PASSWORD_LENGTH' | translate}}
    </span>
    <span *ngIf="passwordConfirmation.errors.pattern" class="p-md-error-multiline-div">
      {{'PASSWORD_RECOVERY.FOR_A_SECURE_PASSWORD' | translate}}
    </span>
  </md-error>
</md-input-container>
1

There are 1 answers

0
Deniss M. On BEST ANSWER

Finally managed to find a solution! I decided to go with NgModelGroup. Here's the directive:

@Directive({
  selector: '[validateEqual][ngModelGroup]',
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => PasswordValidationDirective),
      multi: true
    }
  ]
})
export class PasswordValidationDirective implements Validator {
  @Input('password') public password: string;
  @Input('confirmation') public confirmation: string;

  public validate(fg: FormGroup): { [key: string]: any } {
    const fieldOne = fg.value[this.password];
    const fieldTwo = fg.value[this.confirmation];

    if (!fieldOne || !fieldTwo || fieldOne === fieldTwo ) {
      return null;
    }

    return {valueEquals: false};

  }
}

And here's the HTML:

<div ngModelGroup="passwordGroup"
     #passwordGroup="ngModelGroup"
     validateEqual
     password="password"
     confirmation="passwordConfirmation">
  <div class="row">
    <div class="col-xs-6">
      <md-input-container class="full-width">
        <input mdInput
               type="password"
               required
               ngModel name="password"
               #password="ngModel"
               placeholder="{{'SIGNUP.PASSWORD' | translate}}">
      </md-input-container>
    </div>
    <div class="col-xs-6">
      <md-input-container class="full-width">
        <input mdInput
               type="password"
               required
               ngModel name="passwordConfirmation"
               #passwordConfirmation="ngModel"
               placeholder="{{'SIGNUP.RETYPE_PASSWORD' | translate}}">
      </md-input-container>
    </div>
  </div>
  <div class="row">
    <div class="col-xs-12">
      <md-error *ngIf="passwordGroup.errors">
        <span class="p-text-small-error">{{'SIGNUP.MATCH' | translate}}</span>
      </md-error>
    </div>
  </div>
</div>

The important parts here are the directive itself and the incoming parameters, which can be modified to your liking.