Search code examples
angularangular-validation

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


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>

Solution

  • 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.