Reputation: 12925
Please refer to this question regarding the Comparing fields in validator with Angular 2. Unfortunately Angular 2 changed a bit so that solution seems not working anymore. Here is my code:
import {IonicApp, Page, NavController, NavParams} from 'ionic/ionic'
import {Component} from 'angular2/core'
import {FORM_PROVIDERS, FormBuilder, Validators} from 'angular2/common'
import {ControlMessages} from '../../components/control-messages'
import {ValidationService} from '../../services/validation-service'
@Page({
templateUrl: 'build/pages/account/register.html',
directives: [ControlMessages]
})
export class RegisterPage {
constructor(nav: NavController, private builder: FormBuilder) {
this.nav = nav
this.registerForm = this.builder.group({
'name': ['', Validators.required],
'email': ['', Validators.compose([Validators.required, ValidationService.emailValidator])],
'password': ['', Validators.required],
'repeat': ['', this.customValidator]
}
)
}
register() {
alert(this.registerForm.value.password)
}
private customValidator(control) {
//console.log(this.registerForm.value.password)
//return {isEqual: control.value === this.registerForm.value.password}
return true
}
}
My html:
<ion-content class="account">
<ion-list padding>
<form [ngFormModel]='registerForm' (submit)='register()'>
<div class="centered">
<img class="logo" src="img/logo.png" alt="">
</div>
<div class="spacer" style="height: 20px;"></div>
<ion-input>
<ion-label floating>Name</ion-label>
<input type="text" ngControl='name' id='name'>
<control-messages control="name"></control-messages>
</ion-input>
<ion-input>
<ion-label floating>Email</ion-label>
<input type="email" ngControl='email' id='email'>
<control-messages control="email"></control-messages>
</ion-input>
<ion-input>
<ion-label floating>Password</ion-label>
<input type="password" ngControl='password' id='password' value="">
<control-messages control="password"></control-messages>
</ion-input>
<ion-input>
<ion-label floating>Confirm Password</ion-label>
<input type="password" ngControl='repeat' id='repeat'>
<control-messages control="repeat"></control-messages>
</ion-input>
<button class="calm" full type='submit' [disabled]='!registerForm.valid'>Register</button>
<ion-item style="background-color:transparent;border:none;">
<button class="text-button" clear item-right (click)="gotoLogin()">Have an account already, Login</button>
</ion-item>
</form>
</ion-list>
</ion-content>
But unfortunately, I can't access the password
value in my validating function. If I uncomment console.log(this.registerForm.value.password)
, then I get the following error message:
EXCEPTION: TypeError: Cannot read property 'value' of undefined
Any idea? Thanks.
Upvotes: 65
Views: 86754
Reputation: 202326
I see several problems in your code. You try to use the this
keyword in the validator function and this doesn't correspond to the instance of the component. It's because you reference the function when setting it as a validator function.
Moreover the value associated with a control can be reached in the value
property.
That said, I think that the right way to validate your two fields together is to create a group and associate a validator in it:
import { FormBuilder, Validators } from '@angular/forms';
...
constructor(private fb: FormBuilder) { // <--- inject FormBuilder
this.createForm();
}
createForm() {
this.registerForm = this.fb.group({
'name' : ['', Validators.required],
'email': ['', [Validators.required, Validators.email] ],
'passwords': this.fb.group({
password: ['', Validators.required],
repeat: ['', Validators.required]
}, {validator: this.matchValidator})
});
}
This way you will have access to all controls of the group and not only one and don't need anymore to use the this
keyword... The group's form controls can be accessed using the controls
property of the FormGroup. The FormGroup is provided when validation is triggered. For example:
matchValidator(group: FormGroup) {
var valid = false;
for (name in group.controls) {
var val = group.controls[name].value
(...)
}
if (valid) {
return null;
}
return {
mismatch: true
};
}
See this anwer for more details:
Edit
To display the error, you can simply use the following:
<span *ngIf="!registerForm.passwords.valid" class="help-block text-danger">
<div *ngIf="registerForm.passwords?.errors?.mismatch">
The two passwords aren't the same
</div>
</span>
Upvotes: 71
Reputation: 620
Responding to Zhou Hao person who raised the question of the debate I think that this is what you were looking for because it happened to me the same as you, he said that the variable was undefined and solve it like this:
static comparePassword(control) {
try {
control.parent.value.password;
if (control.parent.value.password == control.value) {
return null;
} else {
return { 'invalidValue': true };
}
} catch (e) {
e.message;
}
}
Upvotes: 0
Reputation: 39
I don't think we need a custom validator for matching passwords, this can be easily achieved by using formname.controls['controlName'].value
.
<input type="password" class="form-control" formControlName="password">
<div class="alert alert-danger" *ngIf="!userForm.controls['password'].valid && userForm.controls['password'].touched">
Enter valid password between 7 and 14 characters.
</div>
<input type="password" class="form-control" formControlName="confPassword">
<div *ngIf="userForm.controls['confPassword'].touched">
<div class="alert alert-danger" *ngIf="userForm.controls['confPassword'].value != userForm.controls['password'].value">
Password do not match
</div>
</div>
In fileName.component.ts
the form control declared as:
'password':[null, Validators.compose([Validators.required, Validators.minLength(7), Validators.maxLength(14))],
'confPassword':[null, Validators.required]
Upvotes: 0
Reputation: 22833
If you want to create directive and use it then see this
Here is the complete directive code
import { Directive, Attribute } from '@angular/core';
import { Validator, NG_VALIDATORS } from '@angular/forms';
@Directive({
selector: '[advs-compare]',
providers: [{provide: NG_VALIDATORS, useExisting: CompareDirective, multi: true}]
})
export class CompareDirective implements Validator {
constructor(@Attribute('advs-compare') public comparer: string){}
validate(c: Control): {[key: string]: any} {
let e = c.root.get(this.comparer);
if(e && c.value !== e.value){
return {"compare": true};
}
return null;
}
}
For more information how to use it, see http://www.advancesharp.com/blog/1226/angular-5-email-compare-password-validation-different-ways
Upvotes: 1
Reputation: 476
This is the easiest way which I follow.I have removed code which are not relevant.
I think this will help you.
auth.component.ts,auth.component.html,auth.component.css
import { Component, OnInit, EventEmitter, Input, Output ,Directive, forwardRef, Attribute,OnChanges, SimpleChanges} from '@angular/core';
import { NG_VALIDATORS,Validator,Validators,AbstractControl,ValidatorFn } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { RegisterUser } from '../shared/models';
@Component({
moduleId: module.id,
selector: 'auth-page',
styleUrls: ['./auth.component.css'],
templateUrl: './auth.component.html'
})
export class AuthComponent implements OnInit {
userRegisterModel: RegisterUser = new RegisterUser();
constructor(private route: ActivatedRoute, private router: Router) { }
ngOnInit() {
this.userRegisterModel.LoginSource = 'M';//Manual,Facebook,Google
}
userRegister() {
console.log(this.userRegisterModel);
}
}
.validation
{
margin:0;
padding-top: 1px;
padding-bottom: 0px;
}
.validation .message
{
font-size: 10px;
color: #de1a16;
}
<form (ngSubmit)="userRegister()" #authRegisterForm="ngForm" novalidate>
<div class="col-md-6 form-group" style="padding-right: 5px;padding-left: 0px;margin-bottom: 0;">
<label>First Name:</label>
<input type="text" class="form-control" required pattern="[a-zA-Z][a-zA-Z ]+" [(ngModel)]="userRegisterModel.firstName" name="firstName"
#firstName="ngModel" placeholder="Your first name">
<div style="display: flex;">
<div [hidden]="firstName.valid || firstName.pristine" class="validation">
<div [hidden]="!firstName.hasError('required')" class="message">Name is required</div>
<div [hidden]="!firstName.hasError('pattern')" class="message">Only alphabets allowed</div>
</div>
</div>
</div>
<div class="col-md-6 form-group" style="padding-right: 5px;padding-left: 0px;margin-bottom: 0;">
<label>Last Name:</label>
<input type="text" class="form-control" required pattern="[a-zA-Z][a-zA-Z ]+" [(ngModel)]="userRegisterModel.lastName" name="lastName"
#lastName="ngModel" placeholder="Your last name">
<div style="display: flex;">
<div [hidden]="lastName.valid || lastName.pristine" class="validation">
<div [hidden]="!lastName.hasError('required')" class="message">Name is required</div>
<div [hidden]="!lastName.hasError('pattern')" class="message">Only alphabets allowed</div>
</div>
</div>
</div>
<div class="col-md-12 form-group" style="padding-right: 5px;padding-left: 0px;margin-bottom: 0;">
<label>Email:</label>
<input type="text" class="form-control" required [(ngModel)]="userRegisterModel.email" name="email" pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$"
#email="ngModel" placeholder="Your email">
<div style="display: flex;">
<div [hidden]="email.valid || email.pristine" class="validation">
<div [hidden]="!email.hasError('required')" class="message">Email is required</div>
<div [hidden]="!email.hasError('pattern')" class="message">Email format should be
<small>
<b>[email protected]</b>
</small>
</div>
</div>
</div>
</div>
<div class="col-md-12 form-group" style="padding-right: 5px;padding-left: 0px;margin-bottom: 0;">
<label>Password:</label>
<input type="password" class="form-control" required [(ngModel)]="userRegisterModel.password" name="password" #password="ngModel"
minlength="6" placeholder="Your strong password" >
<div style="display: flex;">
<div [hidden]="password.valid || password.pristine" class="validation">
<div [hidden]="!password.hasError('minlength')" class="message">Password should be 6digit</div>
<div [hidden]="!password.hasError('required')" class="message">Password is required</div>
</div>
</div>
</div>
<div class="col-md-12 form-group" style="padding-right: 5px;padding-left: 0px;margin-bottom: 0;">
<label>Confirm Password:</label>
<input type="password" class="form-control" required validateEqual="password" [(ngModel)]="userRegisterModel.confirmPassword"
name="confirmPassword" #confirmPassword="ngModel" placeholder="Confirm your password">
<div style="display: flex;">
<div [hidden]="confirmPassword.valid || confirmPassword.pristine" class="validation">
<div class="message">Passwords did not match</div>
</div>
</div>
</div>
<div class="col-md-12 form-group text-right" style="padding-right: 5px;padding-left: 0px;margin-bottom: 0;">
<button type="submit" class="btn btn-primary" [disabled]="!authRegisterForm.form.valid">
Submit form
<i class="icon-arrow-right14 position-right"></i>
</button>
</div>
</form>
registerUser.mocel.ts
export class RegisterUser {
FirstName : number;
LastName : string;
Email: string;
Password : string;
}
password.match.directive.ts
import { Directive, forwardRef, Attribute } from '@angular/core';
import { NG_VALIDATORS,Validator,Validators,AbstractControl,ValidatorFn } from '@angular/forms';
@Directive({
selector: '[validateEqual][formControlName],[validateEqual][formControl],[validateEqual][ngModel]',
providers: [
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => EqualValidator), multi: true }
]
})
export class EqualValidator implements Validator {
constructor( @Attribute('validateEqual') public validateEqual: string) {}
validate(c: AbstractControl): { [key: string]: any } {
// self value (e.g. retype password)
let v = c.value;
// control value (e.g. password)
let e = c.root.get(this.validateEqual);
// value not equal
if (e && v !== e.value) return {
validateEqual: false
}
return null;
}
}
app.module.ts
import { ModuleWithProviders, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
//import { AppRoutingModule } from './app-routing.module';
//import { AppComponent } from './app.component';
//shared components
import { EqualValidator} from './shared/password.match.directive';
@NgModule({
declarations: [
// AppComponent,
//FooterComponent,
// HeaderComponent,
// LoginComponent,
// LayoutComponent,
// AuthComponent,
// UserExistComponent,
// HomeComponent,
EqualValidator
],
imports: [
// BrowserModule,
// AppRoutingModule,
// SharedModule
],
providers: [
// ApiService,
// AuthGuard,
//JwtService,
// UserService,
// HomeAuthResolver,
// NoAuthGuard,
// SharedService
],
bootstrap: [AppComponent]
})
export class AppModule { }
Upvotes: 0
Reputation: 3722
My solution for Angular 4.3.4, which doesn't require additional FormGroup
:
repeatedPassword
checking if passwords are the same password.valueChanges
during form creation, and call .updateValueAndValidity() method
on repeatedPassword
Here is some code:
form: FormGroup
passwordFieldName = 'password'
repeatedPasswordFieldName = 'repeatedPassword'
createForm() {
this.form = this.formBuilder.group({
login: ['', [Validators.required, Validators.minLength(3), Validators.maxLength(255), Validators.email]],
[passwordFieldName]: ['', [Validators.required, Validators.minLength(6), Validators.maxLength(255)]],
[repeatedPasswordFieldName]: ['', [Validators.required, this.samePassword]]
});
this.form
.get(passwordFieldName)
.valueChanges.subscribe(() => {
this.form
.get(repeatedPasswordFieldName).updateValueAndValidity();
})
}
samePassword(control: FormControl) {
if (!control || !control.parent) {
return null;
}
if (control.value !== control.parent.get(passwordFieldName).value) {
return {'passwordMismatch': true}
}
return null;
}
Upvotes: 2
Reputation: 51
Summary
{match: true}
will allow us check if a given control has the error using myControl.hasError('match')
Implementation
import { AbstractControl, ValidatorFn } from '@angular/forms';
import { Subscription } from 'rxjs/Subscription';
export function matchOtherValidator(otherControlName: string): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => {
const otherControl: AbstractControl = control.root.get(otherControlName);
if (otherControl) {
const subscription: Subscription = otherControl
.valueChanges
.subscribe(() => {
control.updateValueAndValidity();
subscription.unsubscribe();
});
}
return (otherControl && control.value !== otherControl.value) ? {match: true} : null;
};
}
Example
this.registerForm = formBuilder.group({
email: ['', [
Validators.required, Validators.email
]],
password: ['', [
Validators.required, Validators.minLength(8)
]],
confirmPassword: ['', [
Validators.required, matchOtherValidator('password')
]]
});
Upvotes: 5
Reputation: 3995
Angular 4.3.3 solution!
You can do it using: [formGroup]
, formGroupName
, formControlName
in html and new FormGroup
, new FormControl
and custom areEqual
method in TS
reg.component.html
<div [formGroup]="userFormPassword">
<div>
<input formControlName="current_password" type="password" placeholder="Current Password">
</div>
<div formGroupName="passwords">
<input formControlName="new_password" type="password" placeholder="New Password">
</div>
<div formGroupName="passwords">
<input formControlName="repeat_new_password" type="password" class="form-control" placeholder="Repeat New Password">
<div class="input-error" *ngIf="
userFormPassword.controls['passwords'].errors &&
userFormPassword.controls['passwords'].errors.areEqual &&
userFormPassword.controls['passwords'].controls.repeat_new_password.touched &&
userFormPassword.controls['passwords'].controls.new_password.touched
">PASSWORDS do not match
</div>
</div>
</div>
reg.component.ts
export class HomeHeaderSettingsModalComponent implements OnInit {
userFormPassword: FormGroup;
// ...
static areEqual(c: AbstractControl): ValidationErrors | null {
const keys: string[] = Object.keys(c.value);
for (const i in keys) {
if (i !== '0' && c.value[ keys[ +i - 1 ] ] !== c.value[ keys[ i ] ]) {
return { areEqual: true };
}
}
}
ngOnInit() {
this.userFormPassword = new FormGroup({
'current_password': new FormControl(this.user.current_password, [
Validators.required,
]),
'passwords': new FormGroup({
'new_password': new FormControl(this.user.new_password, [
Validators.required
]),
'repeat_new_password': new FormControl(this.user.repeat_new_password, [
Validators.required
])
}, HomeHeaderSettingsModalComponent.areEqual)
});
}
}
Upvotes: 9
Reputation: 2027
I found a solution that made me happier as far as consistency of code an error handling:
1st: Create a custom validation class with a static method which does the validation
This method should have a AbstractControl parameter which angular injects
Note that you will pass this in the ConfirmPassword control, so you need to call parent to get to the FormGroup. From there you call formGroup.get('myControl') and get the controls for password and confirm as you named them when you created the form group.
import {AbstractControl} from '@angular/forms';
export class PasswordValidation {
static MatchPassword(AC: AbstractControl) {
const formGroup = AC.parent;
if (formGroup) {
const passwordControl = formGroup.get('Password'); // to get value in input tag
const confirmPasswordControl = formGroup.get('Confirm'); // to get value in input tag
if (passwordControl && confirmPasswordControl) {
const password = passwordControl.value;
const confirmPassword = confirmPasswordControl.value;
if (password !== confirmPassword) {
return { matchPassword: true };
} else {
return null;
}
}
}
return null;
}
}
2nd: Use your Customer Validator just like you use angulars
this.registerForm = this.fb.group({ // <-- the parent FormGroup
Email: ['', Validators.required ],
Username: ['', Validators.required ],
FirstName: ['', Validators.required ],
Password: ['',
[
Validators.required,
Validators.minLength(6)
]
],
Confirm: ['',
[
Validators.required,
PasswordValidation.MatchPassword
]
]
});
Angular will then add the 'matchPassword': true to your Confirm controls errors exactly as it would add 'required' true when missing a value
Upvotes: 2
Reputation: 21
Here's my way using Angular Validators
COMPONENT:
import { UserModel } from '../../settings/users/user.model';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FormRequestModel } from '../Shared/form.model';
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-add-user',
templateUrl: './add-user.component.html',
styleUrls: ['./add-user.component.scss']
})
export class AddUserComponent implements OnInit {
passwordsForm: FormGroup;
user: UserModel;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.passwordsForm = this.fb.group({
inputPassword: ['', Validators.compose([Validators.required, Validators.minLength(6), Validators.maxLength(50)])],
inputPasswordAgain: ['']
});
}
}
HTML:
<form class="form-horizontal" [formGroup]="passwordsForm" novalidate>
<div class="form-group">
<br/>
<label for="inputPassword" class="col-sm-2 control-label">Password</label>
<div class="col-sm-10">
<input type="password" formControlName="inputPassword" class="form-control" id="inputPassword" placeholder="Password">
</div>
</div>
<div class="alert alert-danger" *ngIf="!passwordsForm.controls['inputPassword'].valid && passwordsForm.controls['inputPassword'].touched">Password must contain at least 6 characters!!</div>
<div class="form-group">
<br/>
<label for="inputPasswordAgain" class="col-sm-2 control-label">Password again</label>
<div class="col-sm-10">
<input type="password" formControlName="inputPasswordAgain" class="form-control" id="inputPasswordAgain" placeholder="Password again">
</div>
</div>
<!-- Show div warning element if both inputs does not match the validation rules below -->
<div class="alert alert-danger" *ngIf="passwordsForm.controls['inputPasswordAgain'].touched
&& passwordsForm.controls['inputPasswordAgain'].value !== passwordsForm.controls['inputPassword'].value">
Both passwords must be equal!</div>
Upvotes: 1
Reputation: 41
Well, i searched for answer on this topic and all were too big for my laziness so i did it like this. I think it gets the job done well.
I used the ngModel to bind password and repeatPassword input and then i have shown or hide the div with password comparison message with [hidden] attribute in angular 2.
<label for="usr">Password</label>
<input placeholder="12345" id="password" type="text" class="form-control"
[(ngModel)]="password">
<label for="usr">Repeat pasword</label>
<input placeholder="12345" type="text" class="form-control"
[(ngModel)]="repeatPassword">
<div [hidden]="password == repeatPassword">Passwords do not match!</div>
Upvotes: 2
Reputation: 28661
I've implemented a custom passwords match validator for Angular 4.
Besides checking if two values are matching, it also subscribes to changes from other control and re-validates when either of two controls is updated. Feel free to use it as a reference for your own implementation or just copy it directly.
Here's the link to the solution: https://gist.github.com/slavafomin/17ded0e723a7d3216fb3d8bf845c2f30.
And here I'm providing a copy of the code:
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;
}
}
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.
Upvotes: 27
Reputation: 371
found much simpler solution. Not sure if this is the right way to do it but it works for me
<!-- PASSWORD -->
<ion-item [ngClass]="{'has-error': !signupForm.controls.password.valid && signupForm.controls.password.dirty}">
<ion-input formControlName="password" type="password" placeholder="{{ 'SIGNUP.PASSWORD' | translate }}" [(ngModel)]="registerCredentials.password"></ion-input>
</ion-item>
<!-- VERIFY PASSWORD -->
<ion-item [ngClass]="{'has-error': !signupForm.controls.verify.valid && signupForm.controls.verify.dirty}">
<ion-input formControlName="verify" [(ngModel)]="registerCredentials.verify" type="password" pattern="{{registerCredentials.password}}" placeholder="{{ 'SIGNUP.VERIFY' | translate }}"> </ion-input>
</ion-item>
See
pattern="{{registerCredentials.password}}"
Upvotes: 12
Reputation: 1411
I just want to post my solution:
this.authorizationSettings = formBuilder.group({
currentPassword: [null, Validators.compose([Validators.required, Validators.minLength(8)])],
newPassword: [null, Validators.compose([Validators.required, Validators.minLength(8)])],
repeatNewPassword: [null]
});
this.authorizationSettings.controls.newPassword.valueChanges.subscribe(data => {
if (data) {
data = data.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
}
this.authorizationSettings.controls.repeatNewPassword
.clearValidators();
this.authorizationSettings.controls.repeatNewPassword
.setValidators(Validators.compose([Validators.required, Validators.pattern(data)]));
});
We need to create form group first, then subscribe to first new password field then add validation to repeat field.
Upvotes: 1
Reputation: 4174
By using this library ng2-validation-manager you can easily do this:
this.form = new ValidationManager({
'password' : 'required|rangeLength:8,50',
'repassword' : 'required|equalTo:password'
});
Upvotes: 3
Reputation: 365
Save the password into instance variable.
password = new FormControl('', [Validators.required]);
Then use it in your form group.
this.registrationForm = this.fb.group({
'email': ['', [
Validators.required,
NGValidators.isEmail,
]
],
'password': this.password,
'password2': ['', [Validators.required, this.passwordMatch]]
});
So the function looks like this.
private passwordMatch() {
let that = this;
return (c: FormControl) =>
{
return (c.value == that.password.value) ? null : {'passwordMatch': {valid: false}};
}
}
I know it isnt the best solution, but its working!
Upvotes: 3
Reputation: 841
If you're using RC.5 and can't find ControlGroup, you can try using FormGroup. You may find out more from my answer:
Angular 2 RC.5 form validation for password repeat
Upvotes: 5
Reputation: 1067
Also, as of angular 2 rc4 with forms 0.2.0 the markup and attribute calling out the group name used to encompass the grouped inputs is needed to prevent errors
<div formGroupName="passwords">group input fields here... </div>
Upvotes: 2