126

Is it possible to create a validator which can use multiple values to decide if my field is valid?

e.g. if the customer's preferred contact method is by email then the email field should be required.

Thanks.


Updated with example code...


    import {Component, View} from 'angular2/angular2';
    import {FormBuilder, Validators, formDirectives, ControlGroup} from 'angular2/forms';

    @Component({
        selector: 'customer-basic',
        viewInjector: [FormBuilder]
    })
    @View({
        templateUrl: 'app/components/customerBasic/customerBasic.html',
        directives: [formDirectives]
    })
    export class CustomerBasic {
        customerForm: ControlGroup;

        constructor(builder: FormBuilder) {
            this.customerForm = builder.group({
                firstname: [''],
                lastname: [''],
                validateZip: ['yes'],
                zipcode: ['', this.zipCodeValidator] 
                // I only want to validate using the function below if the validateZip control is set to 'yes'
            });
        }

        zipCodeValidator(control) {
            if (!control.value.match(/\d\d\d\d\d(-\d\d\d\d)?/)) {
                return { invalidZipCode: true };
            }
        }

    }
Stanley De Boer
  • 4,921
  • 1
  • 23
  • 31
Simon
  • 1,751
  • 2
  • 14
  • 11
  • Yes. And if you show us your code we could add a specific answer. – michelem Aug 03 '15 at 13:32
  • I have added a basic example. In the sample code how can I only validate the zip code if the previous validateZip control contains 'yes'? – Simon Aug 10 '15 at 15:32
  • Simon, why not promoting *the* answer to your question? – superjos Dec 07 '16 at 15:41
  • 6
    Ok, to save future visitors of this question a lot of frustration, I highly recommend using this NPM package: https://www.npmjs.com/package/ng2-validation. It has build in `equal` and `equalTo` methods and good documentation! – Michelangelo Jan 03 '17 at 16:02
  • Anyone looking for a sample on this topic - https://github.com/kara/ac-forms/tree/master/src/app/reactive-forms – Manish Jain May 05 '17 at 18:39
  • 2
    Angular documentation: https://angular.io/guide/form-validation#cross-field-validation – ElliotSchmelliot Aug 13 '18 at 17:00
  • @Simon did you find a solution to your problem, I am having similar problem and I dont think the answers here provide solution, whether a certain control is required or not is based on option selected but the answers here are only pointing to matching two controls – Mronzer May 20 '20 at 09:07

15 Answers15

152

To kind of reiterate on the methods other have posted, this is the way I've been creating FormGroup validators that don't involve multiple groups.

For this example, simply provide the key names of the password and confirmPassword fields.

// Example use of FormBuilder, FormGroups, and FormControls
this.registrationForm = fb.group({
  dob: ['', Validators.required],
  email: ['', Validators.compose([Validators.required,  emailValidator])],
  password: ['', Validators.required],
  confirmPassword: ['', Validators.required],
  firstName: ['', Validators.required],
  lastName: ['', Validators.required]
}, {validator: matchingPasswords('password', 'confirmPassword')})

In order for Validators to take parameters, they need to return a function with either a FormGroup or FormControl as a parameter. In this case, I'm validating a FormGroup.

function matchingPasswords(passwordKey: string, confirmPasswordKey: string) {
  return (group: FormGroup): {[key: string]: any} => {
    let password = group.controls[passwordKey];
    let confirmPassword = group.controls[confirmPasswordKey];

    if (password.value !== confirmPassword.value) {
      return {
        mismatchedPasswords: true
      };
    }
  }
}

Technically, I could have validated any two values if I knew their keys, but I prefer to name my Validators the same as the error they will return. The function could be modified to take a third parameter that represents the key name of the error returned.

Updated Dec 6, 2016 (v2.2.4)

Full Example: https://embed.plnkr.co/ukwCXm/

cyber_dave
  • 2,019
  • 1
  • 16
  • 21
  • @Dave <> Did you actually mean <>, or what? Thanks – superjos Jun 03 '16 at 23:35
  • This doesn't seem to remove the alert markup when the passwords match with Angular 2 RC.1 – datatype_void Jun 08 '16 at 02:44
  • "ControlGroups" doesn't appear to exist in 2.0. I used 'FormGroup' – Stephen Dec 06 '16 at 23:08
  • @superjos I did mean to say that. Some developers opt to create a nested `FormGroup` to handle multi-field validation instead of putting a `Validator` on the whole thing. – cyber_dave Dec 07 '16 at 05:11
  • I do not like that example because on every keystroke on any field change the password validation is executed! – Pascal Feb 21 '17 at 17:26
  • I completely agree! However, adding a [debounce](https://manuel-rauber.com/2015/12/31/debouncing-angular-2-input-component/) is outside the scope of this question. It's up to *you* to optimize/customize this example any further. – cyber_dave Feb 21 '17 at 17:34
  • I left this comment because I am in general disappointed by the angular 2 forms devs how the 'cross field' aka 'conditional fields' validation is handled. They either use your approach or a sub-formgroup approach like debora kurata (ng2 doc expert) is doing. I did the same and run into beta-software problems... http://stackoverflow.com/questions/42354697/formcontrols-of-nested-formgroup-are-ng-valid-although-fromgroup-is-ng-invalid Maybe you have an answer? – Pascal Feb 21 '17 at 19:40
  • This validation has a bug, when you enter confirm password first, then password field, validation won't be triggered. – VsMaX Apr 19 '17 at 08:06
  • @VsMaX Which part of validation? Overall or field specific? I am unable to recreate this issue. – cyber_dave Apr 19 '17 at 15:29
  • Thanks for help, I've seen your plunker and it works indeed, my application was returning wrong value from validator. Sorry, it's my mistake. – VsMaX Apr 19 '17 at 15:56
  • 1
    What if we have password, confirmPassword and email and Confirm Email? `[{validator: matchingPasswords('password', 'confirmPassword')},{validator: matchingEmail('email', 'confirmemail')}]` I tried this but it is not working. Any suggestions ? @Dave – Sharan Ainapurapu Jul 26 '17 at 16:07
  • I fixed it by passing parms like this `{validator: validatorService.isFieldMatching([{keyField:"email",confirmKeyField:"email1"},{keyField:"password",confirmKeyField:"password1"}])}` and had forEach on the services side – Sharan Ainapurapu Jul 27 '17 at 04:34
  • What if I have password, confirmpassword and email, confirm email in same form. Is it possible to use everything in a one method of validator ? – iMalek Oct 12 '17 at 15:46
  • @MalekI You can use `Validators.compose` to use multiple validators for this from. My example is very generic and could work for any type of field you want to match. If you go with that, I'd rename the validator to something like `matchingFields`, refactor accordingly, and do something like: `Validators.compose([Validators.matchingFields('password', 'confirmPassword'), Validators.matchingFields('email', 'confirmEmail')]` – cyber_dave Oct 13 '17 at 16:49
  • @cyber_dave I tried the way as you mentioned but it is not working for me . Might be some mistake i have done . can you please help me out. – shivam Mar 01 '18 at 17:27
  • has anyone did for more than two fields as like @Sharan Ainapurapu mentioned above? – Ramsk Nov 29 '18 at 14:16
52

Dave's answer was very, very helpful. However, a slight modification might help some people.

In case you need to add errors to the Control fields, you can keep the actual construction of the form and validators:

// Example use of FormBuilder, ControlGroups, and Controls
this.registrationForm= fb.group({
  dob: ['', Validators.required],
  email: ['', Validators.compose([Validators.required,  emailValidator])],
  password: ['', Validators.required],
  confirmPassword: ['', Validators.required],
  firstName: ['', Validators.required],
  lastName: ['', Validators.required]
}, {validator: matchingPasswords('password', 'confirmPassword')})

Instead of setting an error on the ControlGroup, do so on the actual field as follows:

function matchingPasswords(passwordKey: string, passwordConfirmationKey: string) {
  return (group: ControlGroup) => {
    let passwordInput = group.controls[passwordKey];
    let passwordConfirmationInput = group.controls[passwordConfirmationKey];
    if (passwordInput.value !== passwordConfirmationInput.value) {
      return passwordConfirmationInput.setErrors({notEquivalent: true})
    }
  }
}
Community
  • 1
  • 1
Louis Cruz
  • 1,703
  • 2
  • 15
  • 20
  • 6
    Use `passwordConfirmationInput.setErrors(passwordConfirmationInput.validator(passwordConfirmationInput))` in the `else` branch to make it update correctly when a change to `passwordInput` makes the data valid. – andraaspar Sep 01 '16 at 15:49
  • @andraaspar I tried that but I got the error `TypeError: passwordConfirmationInput.validator is not a function`. It's because I didn't explicitly create the FormControl with Validators.required. I left the validators blank and instead used the "required" attribute on the input. – beardedlinuxgeek Nov 01 '16 at 18:44
  • 6
    This was helpful, but I noticed that the angular documentation has a return type of `{[key: string]: any}`, which `setErrors(...)` doesn't return (anymore?). Also `setErrors(...)` overwrites any errors that are already present, so I appended to the current error object like: `let errors = formGroup.controls[passwordConfirmationKey].errors;` and `if(!errors) errors={};` and `errors['notEquivalent'] = true;` and `formGroup.controls[dateControlFirst].setErrors(errors);` – Stephen Dec 07 '16 at 17:01
36

When implementing validators for multiple form fields, you will have to make sure, that validators are re-evaluated when each of the form control is updated. Most of the examples doesn't provide a solution for such scenario, but this is very important for data consistency and correct behavior.

Please see my implementation of a custom validator for Angular 2, which takes this into account: https://gist.github.com/slavafomin/17ded0e723a7d3216fb3d8bf845c2f30.

I'm using otherControl.valueChanges.subscribe() to listen for changes in other control and thisControl.updateValueAndValidity() to trigger another round of validation when other control is changed.


I'm copying a code below for future reference:

match-other-validator.ts

import {FormControl} from '@angular/forms';


export function matchOtherValidator (otherControlName: string) {

  let thisControl: FormControl;
  let otherControl: FormControl;

  return function matchOtherValidate (control: FormControl) {

    if (!control.parent) {
      return null;
    }

    // Initializing the validator.
    if (!thisControl) {
      thisControl = control;
      otherControl = control.parent.get(otherControlName) as FormControl;
      if (!otherControl) {
        throw new Error('matchOtherValidator(): other control is not found in parent group');
      }
      otherControl.valueChanges.subscribe(() => {
        thisControl.updateValueAndValidity();
      });
    }

    if (!otherControl) {
      return null;
    }

    if (otherControl.value !== thisControl.value) {
      return {
        matchOther: true
      };
    }

    return null;

  }

}

Usage

Here's how you can use it with reactive forms:

private constructForm () {
  this.form = this.formBuilder.group({
    email: ['', [
      Validators.required,
      Validators.email
    ]],
    password: ['', Validators.required],
    repeatPassword: ['', [
      Validators.required,
      matchOtherValidator('password')
    ]]
  });
}

More up-to-date validators could be found here: moebius-mlm/ng-validators.

Slava Fomin II
  • 26,865
  • 29
  • 124
  • 202
  • Nice answer!! I've been looking for a solution like this for hours! Please consider a little change: Instead of losing "this" reference when returning a function, return a function like this: return (control: FormControl) => { /*code*/} – Vingtoft May 23 '17 at 15:16
  • Glad I could help. Why do you need reference to `this` for? Actually, it's good to have a named function for debugging purposes. – Slava Fomin II May 24 '17 at 13:31
  • Performance? it's working but performance-wise I don't think it's good solution. Updating 'thisControl' when 'theOtherControl's value is changed will create a loop, wouldn't it? – nightElf91 Jul 20 '19 at 05:02
  • When should it be unsubscribe? `othercontrol.valuechanges.subscribe` did not get unsubscribed anywhere. – juana pu Mar 04 '20 at 12:03
  • @juanapu I would presume that Angular will terminate the `valueChanges` observable when the `otherControl` will be destroyed, which will cause the subscription to be also terminated. However, your concerns could be valid. I would suggest to thoroughly debug this code with the latest version of Angular using various test cases. Please, report back if you would find any issues. – Slava Fomin II Mar 04 '20 at 12:28
  • otherControl.valueChanges.pipe(take(1)).subscribe(() => { // This will solve the performance issue – Michalis Nov 01 '20 at 20:43
  • finally This is the correct answer! – java-addict301 Jul 08 '22 at 19:09
25

I'm using Angular 2 RC.5 but couldn't find the ControlGroup, based on the helpful answer from Dave. I found that FormGroup works instead. So I did some minor updates on Dave's code, and thought I'd share with others.

In your component file, add an import for FormGroup:

import {FormGroup} from "@angular/forms";

Define your inputs in case you need to access the form control directly:

oldPassword = new FormControl("", Validators.required);
newPassword = new FormControl("", Validators.required);
newPasswordAgain = new FormControl("", Validators.required);

In your constructor, instantiate your form:

this.form = fb.group({
  "oldPassword": this.oldPassword,
  "newPassword": this.newPassword,
  "newPasswordAgain": this.newPasswordAgain
}, {validator: this.matchingPasswords('newPassword', 'newPasswordAgain')});

Add the matchingPasswords function in your class:

matchingPasswords(passwordKey: string, passwordConfirmationKey: string) {
  return (group: FormGroup) => {
    let passwordInput = group.controls[passwordKey];
    let passwordConfirmationInput = group.controls[passwordConfirmationKey];

    if (passwordInput.value !== passwordConfirmationInput.value) {
      return passwordConfirmationInput.setErrors({notEquivalent: true})
    }
  }
}

Hope this helps those who are using RC.5. Note that I haven't tested on RC.6 yet.

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Chang
  • 831
  • 8
  • 6
  • @Sam Did you change something to make it work with the final version? it's not working for me.. It says: Argument of type '{ validator: (group: FormGroup) => void; }' is not assignable to parameter of type 'ValidatorFn'. – xtof Feb 22 '17 at 15:29
  • No, I did not need to change anything - for me the sample code above worked with Angular2 final. Are you using the exact code like above? – Sam Feb 23 '17 at 16:45
  • Good solution @Chang. If you change your password after filling your confirm password. The validation does not work. You can try ```if (passwordInput.value !== passwordConfirmationInput.value) { return passwordConfirmationInput.setErrors({ notEquivalent: true }); } else { return passwordConfirmationInput.setErrors(null); }``` – Mario Shtika Mar 21 '18 at 08:42
  • It's also nice to setErrors() and markAsTouched() the other field, so both gets red. – NaN Jun 29 '21 at 21:21
17

To expand on matthewdaniel's answer since it's not exactly correct. Here is some example code which shows how to properly assign a validator to a ControlGroup.

import {Component} from angular2/core
import {FormBuilder, Control, ControlGroup, Validators} from 'angular2/common'

@Component({
  selector: 'my-app',
  template: `
    <form [ngFormModel]="form">
      <label for="name">Name:</label>
      <input id="name" type="text" ngControl="name">
      <br>
      <label for="email">Email:</label>
      <input id="email" type="email" ngControl="email">
      <br>
      <div ngControlGroup="matchingPassword">
        <label for="password">Password:</label>
        <input id="password" type="password" ngControl="password">
        <br>
        <label for="confirmPassword">Confirm Password:</label>
        <input id="confirmPassword" type="password" ngControl="confirmPassword">
      </div>
    </form>
    <p>Valid?: {{form.valid}}</p>
    <pre>{{form.value | json}}</pre>
  `
})
export class App {
  form: ControlGroup
  constructor(fb: FormBuilder) {
    this.form = fb.group({
      name: ['', Validators.required],
      email: ['', Validators.required]
      matchingPassword: fb.group({
        password: ['', Validators.required],
        confirmPassword: ['', Validators.required]
      }, {validator: this.areEqual})
    });
  }

  areEqual(group: ControlGroup) {
    let val;
    let valid = true;

    for (name in group.controls) {
      if (val === undefined) {
        val = group.controls[name].value
      } else {
        if (val !== group.controls[name].value) {
          valid = false;
          break;
        }
      }
    }

    if (valid) {
      return null;
    }

    return {
      areEqual: true
    };
  }
}

Here's a working example: http://plnkr.co/edit/Zcbg2T3tOxYmhxs7vaAm?p=preview

superjos
  • 12,189
  • 6
  • 89
  • 134
Cody L.
  • 338
  • 2
  • 13
  • what if we add radiobuttons and checkbox how to get the value of these two ? – Pardeep Jain Dec 18 '15 at 09:34
  • 3
    `ControlGroup` is removed in favor of `FormGroup` for anyone looking at this. [Docs](https://angular.io/docs/ts/latest/api/forms/index/FormGroup-class.html) and [Learn Angular2 Example](http://learnangular2.com/forms/#controlgroup) – sofly Feb 23 '17 at 17:23
16

Lots of digging in angular source but I've found a better way.

constructor(...) {
    this.formGroup = builder.group({
        first_name:        ['', Validators.required],
        matching_password: builder.group({
            password: ['', Validators.required],
            confirm:  ['', Validators.required]
        }, this.matchPassword)
    });

    // expose easy access to passworGroup to html
    this.passwordGroup = this.formGroup.controls.matching_password;
}

matchPassword(group): any {
    let password = group.controls.password;
    let confirm = group.controls.confirm;

    // Don't kick in until user touches both fields   
    if (password.pristine || confirm.pristine) {
      return null;
    }

    // Mark group as touched so we can add invalid class easily
    group.markAsTouched();

    if (password.value === confirm.value) {
      return null;
    }

    return {
      isValid: false
    };
}

HTML portion for password group

<div ng-control-group="matching_password" [class.invalid]="passwordGroup.touched && !passwordGroup.valid">
    <div *ng-if="passwordGroup.touched && !passwordGroup.valid">Passwords must match.</div>
    <div class="form-field">
        <label>Password</label>
        <input type="password" ng-control="password" placeholder="Your password" />
    </div>
    <div class="form-field">
        <label>Password Confirmation</label>
        <input type="password" ng-control="confirm" placeholder="Password Confirmation" />
    </div>
</div>
matthewdaniel
  • 1,842
  • 4
  • 21
  • 35
  • When validation for matching_password is run is the firstName control also evaluated? Which I would not want! – Pascal Jan 27 '18 at 14:41
2

Here is another option that I was able to come up with that isn't dependent on an entire or sub ControlGroup but is tied directly to each Control.

The problem I had was the controls that were dependent on each other weren't hierarchically together so I was unable to create a ControlGroup. Also, my CSS was setup that each control would leverage the existing angular classes to determine whether to display error styling, which was more complicated when dealing with a group validation instead of a control specific validation. Trying to determine if a single control was valid was not possible since the validation was tied to the group of controls and not each individual control.

In my case I wanted a select box's value to determine if another field would be required or not.

This is built using the Form Builder on the component. For the select model instead of directly binding it to the request object's value I have bound it to get/set functions that will allow me to handle "on change" events for the control. Then I will be able to manually set the validation for another control depending on the select controls new value.

Here is the relevant view portion:

<select [ngFormControl]="form.controls.employee" [(ngModel)]="employeeModel">
  <option value="" selected></option>
  <option value="Yes">Yes</option>
  <option value="No">No</option>
</select>
...
<input [ngFormControl]="form.controls.employeeID" type="text" maxlength="255" [(ngModel)]="request.empID" />

The relevant component portion:

export class RequestComponent {
  form: ControlGroup;
  request: RequestItem;

  constructor(private fb: FormBuilder) {
      this.form = fb.group({
        employee: new Control("", Validators.required),
        empID: new Control("", Validators.compose([Validators.pattern("[0-9]{7}"]))
      });

  get employeeModel() {
    return this.request.isEmployee;
  }

  set employeeModel(value) {
    this.request.isEmployee = value;
    if (value === "Yes") {
      this.form.controls["empID"].validator = Validators.compose([Validators.pattern("[0-9]{7}"), Validators.required]);
      this.form.controls["empID"].updateValueAndValidity();
    }
    else {
      this.form.controls["empID"].validator = Validators.compose([Validators.pattern("[0-9]{7}")]);
      this.form.controls["empID"].updateValueAndValidity();
    }
  }
}

In my case I always had a pattern validation tied to the control so the validator is always set to something but I think you can set the validator to null if you don't have any validation tied to the control.

UPDATE: There are other methods of capturing the model change like (ngModelChange)=changeFunctionName($event) or subscribing to control value changes by using this.form.controls["employee"].valueChanges.subscribe(data => ...))

Daniel Sara
  • 311
  • 1
  • 4
2

I tried most of these answers but none of them worked for me. I found a working example here https://scotch.io/@ibrahimalsurkhi/match-password-validation-with-angular-2

shah
  • 1,163
  • 2
  • 19
  • 40
1

Was looking for this as well and ended up using equalTo from ng2-validation package (https://www.npmjs.com/package/ng2-validation)

Here is an example: Template Driven:

<input type="password" ngModel name="password" #password="ngModel" required/>
<p *ngIf="password.errors?.required">required error</p>
<input type="password" ngModel name="certainPassword" #certainPassword="ngModel" [equalTo]="password"/>
<p *ngIf="certainPassword.errors?.equalTo">equalTo error</p>

Model Driven:

let password = new FormControl('', Validators.required);
let certainPassword = new FormControl('', CustomValidators.equalTo(password));

this.form = new FormGroup({
  password: password,
  certainPassword: certainPassword
});

Template:

<form [formGroup]="form">
  <input type="password" formControlName="password"/>
  <p *ngIf="form.controls.password.errors?.required">required error</p>
  <input type="password" formControlName="certainPassword"/>
  <p *ngIf="form.controls.certainPassword.errors?.equalTo">equalTo error</p>
</form>
Baidaly
  • 1,829
  • 1
  • 15
  • 16
1

Here is my version I used for ensuring an age in one field is greater than or equal to the age in another field. I'm using form groups as well, so I use the group.get function rather than group.controls[]

import { FormGroup } from '@angular/forms';

export function greaterThanOrEqualTo(sourceKey: string, targetKey: string) {
    return (group: FormGroup) => {
        let sourceInput = group.get(sourceKey);
        let targetInput = group.get(targetKey);

        console.log(sourceInput);
        console.log(targetInput);

        if (targetInput.value < sourceInput.value) {
            return targetInput.setErrors({ notGreaterThanOrEqualTo: true })
        }
    }
}

And in the component:

    this.form = this._fb.group({

        clientDetails: this._fb.group({
            currentAge: ['', [Validators.required, Validators.pattern('^((1[89])|([2-9][0-9])|100)$')]],
            expectedRetirementAge: ['', [Validators.required]]
        }),

    },
    {
        validator: greaterThanOrEqualTo('clientDetails.currentAge', 'clientDetails.expectedRetirementAge')
    });
Adam Hockemeyer
  • 111
  • 1
  • 3
0

i think your best bet, for now, is to create a formgroup to hold your controls. When you instantiate your Control pass in the function to validate it. example:

    this.password = new Control('', Validators.required);
    let x = this.password;
    this.confirm = new Control('', function(c: Control){
        if(typeof c.value === 'undefined' || c.value == "") return {required: "password required"};
        if(c.value !== x.value)
            return {error: "password mismatch"};
        return null;
    });

i know this is higly dependant on the version of angularjs2 you are running. This was tested against 2.0.0-alpha.46

If anyone has a better sugestion such as writing a custom validator (which may be the best way to go) it is welcome.

EDIT

you can also use ControlGroup and validate that group entirelly.

this.formGroup = new ControlGroup({}, function(c: ControlGroup){
        var pass: Control = <Control>c.controls["password"];
        var conf: Control = <Control>c.controls["confirm"];
        pass.setErrors(null, true);
        if(pass.value != null && pass.value != ""){
            if(conf.value != pass.value){
                pass.setErrors({error: "invalid"}, true);
                return {error: "error"};
            }
        }
        return null;
    });

Just edit the messages according to your domain.

0

Louis Cruz's answer was very helpful for me.

To complete just add in the else the setErrors reset : return passwordConfirmationInput.setErrors(null);

And all works fine !

Thanks you,

Regards,

TGA

Community
  • 1
  • 1
TGA
  • 73
  • 1
  • 7
0

Angular 8 Example of validating on the password confirmation field

FYI: this will not update the validation on the passwordConfirm field if the main "password" field is changed after this validation has passed. But, you can invalidate the password confirm field when a user types into the password field

<input
  type="password"
  formControlName="password"
  (input)="registerForm.get('passwordConfirm').setErrors({'passwordMatches': true})"
/>

register.component.ts

import { PasswordConfirmValidator } from './password-confirm-validator';
export class RegisterComponent implements OnInit {
  registerForm: FormGroup = this.createRegisterForm({
    username: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', [
      Validators.required,
      Validators.pattern('^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$'),
      Validators.minLength(8)
    ]),
    passwordConfirm: new FormControl('', [
      Validators.required,
      PasswordConfirmValidator //custom validator
    ])
  });
}

password-confirm-validator.ts

import { AbstractControl } from '@angular/forms';

export function PasswordConfirmValidator(control: AbstractControl) {
  if(void 0 === control){ return null; }
  if(
    void 0 !== control.parent &&
    void 0 !== control.parent.controls &&
    void 0 !== control.parent.controls['password'] &&
    control.parent.controls['password'].value === control.value
  ){
    return null;
  }
  return {passwordMatches: true};
}

register.component.html

{{registerForm.get('passwordConfirm').hasError('passwordMatches')}}
Tim Joyce
  • 4,487
  • 5
  • 34
  • 50
-2

I would suggest using the library ng-form-rules. It is an awesome library for creating all different kinds of forms with validation logic decoupled from the component and that can depend on value changes of other areas in the form. They have great documentation, examples, and a video that shows a bunch of its functionality. Doing validation like this what you're trying to do is trivial.

You can check out their README for some high level info and a basic example.

Chris Knight
  • 1,448
  • 1
  • 15
  • 21
  • 2
    I don't like the idea that there is a library for everything... libraries are not the solution to this problem. Often you will create new problems with just using another library, also you have to keep the stuff up to date when Angular updates. Why not using angular forms as intended by the framework? – seawave_23 Mar 15 '19 at 11:37
-3

Angular 4 password match validation rules.

If you need to errors control fields then you can do it.

createForm() {
    this.ngForm = this.fb.group({
       'first_name': ["", Validators.required ],
       'last_name' : ["", Validators.compose([Validators.required, Validators.minLength(3)]) ],
       'status' : ['active', Validators.compose([Validators.required])],
       'phone':[null],
       'gender':['male'],
       'address':[''],
       'email':['', Validators.compose([
          Validators.required, 
          Validators.email])],
       'password':['', Validators.compose([Validators.required])],
       'confirm_password':['', Validators.compose([Validators.required])]
    }, {validator: this.matchingPassword('password', 'confirm_password')});
  }

Then your need to declaration this this method in constructor method Like as.

constructor(
    private fb: FormBuilder

    ) {
    this.createForm();
  }

Instead of setting an error on the ControlGroup, do so on the actual field as follows:

    matchingPassword(passwordKey: string, confirmPasswordKey: string) {
  return (group: FormGroup): {[key: string]: any} => {
    let password = group.controls[passwordKey];
    let confirm_password = group.controls[confirmPasswordKey];

    if (password.value !== confirm_password.value) {
      return {        
        mismatchedPasswords: true
      };
    }
  }
}

HTML portion for password group

<form [formGroup]="ngForm" (ngSubmit)="ngSubmit()">
    <div class="form-group">
            <label class="control-label" for="inputBasicPassword"> Password <span class="text-danger">*</span></label>
                <input type="password" class="form-control" formControlName="password" placeholder="Password" name="password" required>
                <div class="alert text-danger" *ngIf="!ngForm.controls['password'].valid && ngForm.controls['password'].touched">This Field is Required.</div>
            </div>
            {{ngForm.value.password | json}}
            <div class="form-group">
            <label class="control-label" for="inputBasicPassword">Confirm Password <span class="text-danger">*</span></label>
                <input type="password" class="form-control" name="confirm_password" formControlName="confirm_password" placeholder="Confirm Password" match-password="password">

    <div class='alert text-danger' *ngIf="ngForm.controls.confirm_password.touched && ngForm.hasError('mismatchedPasswords')">
              Passwords doesn't match.
      </div>
    </div>
<button type="submit" [disabled]="!ngForm.valid" class="btn btn-primary ladda-button" data-plugin="ladda" data-style="expand-left" disabled="disabled"><span class="ladda-label">
            <i class="fa fa-save"></i>  Create an account
        <span class="ladda-spinner"></span><div class="ladda-progress" style="width: 0px;"></div>
        </span><span class="ladda-spinner"></span></button>
</form>
Md.Jewel Mia
  • 3,345
  • 3
  • 19
  • 24