I am trying To create custom Validation to match password and ConfirmPassword.
But I am getting the error.
I am new to angular. Please ignore my ignorance.
Is it a good idea to create custom validation to match password and Confirm password? or is there a better way out?
Here is the code.
Please help
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators, AbstractControl, ValidatorFn, ValidationErrors, FormBuilder } from '#angular/forms';
#Component({
selector: 'app-sample-form',
templateUrl: './sample-form.component.html',
styleUrls: ['./sample-form.component.scss']
})
export class SampleFormComponent implements OnInit {
formattedMessage: string;
passMatch: Boolean;
formValid: boolean;
constructor() { }
ngOnInit() {
this.onChanges();
}
sample: FormGroup = new FormGroup({
inpt: new FormControl(null, Validators.required),
pass: new FormControl("", Validators.required),
cpass: new FormControl("", [Validators.required, this.passwordMatch]),
iagree: new FormControl(null, Validators.nullValidator)
});
onChanges(): void {
this.sample.valueChanges.subscribe(val => {
console.log("======================================");
console.log("PASS : " + this.sample.controls.pass.value);
console.log("CPASS : " + this.sample.controls.cpass.value);
if (this.sample.controls.pass.value == this.sample.controls.cpass.value) {
this.passMatch = true;
console.log("MATCHED");
} else {
this.passMatch = false;
console.log("MIS-MATCHED");
}
this.formValid = this.sample.valid;
console.log("PASS MATCH : " + String(this.passMatch));
console.log("FORM VALID : " + String(this.formValid));
});
}
passwordMatch(group: FormGroup): ValidationErrors | null {
let pass = group.controls.pass.value;
let confirmPass = group.controls.cpass.value;
return pass === confirmPass ? null : { notSame: true }
}
sumbit() {
}
}
I am Getting this error :
ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'pass' of undefined
TypeError: Cannot read property 'pass' of undefined
at passwordMatch (sample-form.component.ts:51)
at forms.js:1480
at Array.map (<anonymous>)
at _executeValidators (forms.js:1476)
at FormControl.validator (forms.js:1418)
at FormControl._runValidator (forms.js:4089)
at FormControl.updateValueAndValidity (forms.js:4050)
at new FormControl (forms.js:4656)
at new SampleFormComponent (sample-form.component.ts:25)
at createClass (core.js:31985)
at resolvePromise (zone-evergreen.js:797)
at resolvePromise (zone-evergreen.js:754)
at zone-evergreen.js:858
at ZoneDelegate.invokeTask (zone-evergreen.js:391)
at Object.onInvokeTask (core.js:39680)
at ZoneDelegate.invokeTask (zone-evergreen.js:390)
at Zone.runTask (zone-evergreen.js:168)
at drainMicroTaskQueue (zone-evergreen.js:559)
-Ashish
You are assigning your validator to the formControl cpass, so the formControl you will get through the arguments is only cpass. That validator needs the parent level to be able to access both controls (pass and cpass). Try this instead:
sample: FormGroup = new FormGroup({
inpt: new FormControl(null, Validators.required),
pass: new FormControl("", Validators.required),
cpass: new FormControl("", [Validators.required]),
iagree: new FormControl(null, Validators.nullValidator)
}, {validators: [this.passwordMatch]});
That should do the trick. The error will be in sample formgroup, not in the formControl, which in my opinion makes sense because it affects to more than one form controls.
You need to bind the passwordMatch function because angular will call it in different context
try
cpass: new FormControl("", [Validators.required, this.passwordMatch.bind(this)]),
instead of
cpass: new FormControl("", [Validators.required, this.passwordMatch]),
Updated answer:
You need to do both bind the passwordMatch method and add pass and cpass in the separate form group with generic validator
something like this:
sample = new FormGroup({
inpt: new FormControl(null, Validators.required),
iagree: new FormControl(null, Validators.nullValidator),
confirmPasswordForm: new FormGroup({
pass: new FormControl('', Validators.required),
cpass: new FormControl('', Validators.required),
}, {
validator: this.passwordMatch.bind(this),
})
});
But you need create nested form in you html
This actually solved the problem.
to use the keyword "root"
passwordMatch(control: AbstractControl): ValidationErrors | null {
const pass = control.root.get('pass');
const cpass = control.root.get('cpass');
if (pass != null && cpass != null) {
console.log("Pass : ", pass.value);
console.log("cpass : ", cpass.value);
if (pass.value != cpass.value) {
console.log("MISMATCH");
return { PasswordMismatch: 'Password Mismatch' }
} else {
console.log("MATCH");
return null;
}
} else {
console.log("MISMATCH2");
return { PasswordMismatch: 'Password Mismatch' };
}
}
Related
My reactive form is almost done. At least it's basic logic. I have only one problem. According to the task I need all inputs to be set using previous data that user entered, if that data exists of course. The data should be kept and stored and rendered if components are switched. I mannaged to save all data into an object in a component, but after page refresh everything is gone.
How can I tackle with this problem?
My code is as follows:
import { Component, OnInit, ViewChild, ElementRef } from '#angular/core';
import { FormGroup, FormControl, FormArray, Validators } from '#angular/forms';
#Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent implements OnInit {
signUpForm: FormGroup;
countries = [
new FormControl('USA'),
new FormControl('India')
];
savedForm: {
city: string,
code: number,
country: string,
email: string,
firstName: string,
id: string,
lastName: string,
phone: number,
state: string;
}
statesUSA = [new FormControl('New York'), new FormControl('California')];
statesIndia = [new FormControl('Andhra Pradesh'), new FormControl('Goa')]
citiesNY = [new FormControl('Albany'), new FormControl('New York City')];
citiesCali = [new FormControl('Sacramento'), new FormControl('Los Angeles'), new FormControl('San Francisco')];
citiesAndhra = [new FormControl('Visakhapatnam'), new FormControl('Amaravati')];
citiesGoa = [new FormControl('Panaji'), new FormControl('Vasco da Gama')];
#ViewChild('phoneInput', {static: false}) phoneInput: ElementRef;
public mask:any = {
mask: '+{38}(0__)000-00-00',
lazy: false
}
constructor() { }
ngOnInit() {
this.signUpForm = new FormGroup({
'firstName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'email': new FormControl(null, [Validators.required, Validators.email, Validators.pattern(/^\S{2,255}#\S+\.\S+$/iu)]),
'country': new FormControl(null, Validators.required),
'phone': new FormControl(null),
'lastName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'id': new FormControl(null, [Validators.required, Validators.pattern(/\b[A-Za-z_]{5,30}\b/)]),
'state': new FormControl(null, Validators.required),
'city': new FormControl(null, Validators.required),
'code': new FormControl(null, [Validators.pattern(/\b[A-Za-z_0-9]{1,10}\b/)])
});
this.signUpForm.setValue(this.savedForm);
}
onBlur(blur: boolean) {
}
onSubmit() {
if(this.signUpForm.status === 'VALID') {
this.
savedForm = this.signUpForm.value;
console.log(this.savedForm);
}
}
onReset() {
}
onChange() {
(<FormGroup>this.signUpForm.get('state').value) = null;
(<FormGroup>this.signUpForm.get('city').value) = null;
}
onOpen(controlName: string) {
}
}
You should use Service, components shouldn't fetch or save data directly they should focus on presenting data and delegate data access to a service.
Step 1
Create a service :
src/services/storeService.ts
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class StoreService {
public savedForm
}
When you provide the service at the root level, Angular creates a single, shared instance of StoreService and injects into any class that asks for it. Registering the provider in the #Injectable metadata also allows Angular to optimize an app by removing the service if it turns out not to be used after all.
Step 2
In your component inject the newly created service in the constructor:
#Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent {
signUpForm: FormGroup;
constructor(private storeService: StoreService) { }
ngOnInit() {
this.signUpForm = new FormGroup({
'firstName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'email': new FormControl(null, [Validators.required, Validators.email, Validators.pattern(/^\S{2,255}#\S+\.\S+$/iu)]),
'country': new FormControl(null, Validators.required),
'phone': new FormControl(null),
'lastName': new FormControl(null, [Validators.required, Validators.pattern(/^[а-яА-ЯёЁіІїЇ]{2,32}$/iu)]),
'id': new FormControl(null, [Validators.required, Validators.pattern(/\b[A-Za-z_]{5,30}\b/)]),
'state': new FormControl(null, Validators.required),
'city': new FormControl(null, Validators.required),
'code': new FormControl(null, [Validators.pattern(/\b[A-Za-z_0-9]{1,10}\b/)])
});
if (this.storeService.savedForm) this.signUpForm.setValue(this.storeService.savedForm)
}
onSubmit() {
if (this.signUpForm.status === 'VALID') {
this.storeService.savedForm = this.signUpForm
}
}
}
When I try assigning a moment to a new variable it changes the value without me modifying it.
I have tried everything from forcing the use of UTC and settings timezones. It just keeps changing the value.
#Component({
selector: 'app-appointment-create',
templateUrl: './appointment-create.component.html',
styleUrls: ['./appointment-create.component.css']
})
export class AppointmentCreateComponent implements OnInit {
appointment: CreateAppointmentDto;
form: FormGroup;
private formSubmitted: boolean;
tasks: Task[];
availability: Availability[];
task: number;
availablemoments: Moment[];
constructor(
private titleService: Title,
private router: Router,
private appointmentService: AppointmentService,
private taskService: TasksService,
private webLogger: WebloggerService,
private availabilityService: AvailabilityService,
) {
this.appointment = new CreateAppointmentDto();
}
dateFilter = (d: Moment): boolean => {
return this.availability.filter(s => s.timestampstart.isSame(d, 'day')).length >= 1;
}
ngOnInit() {
this.titleService.setTitle('New Appointment');
this.taskService.getActiveTasks().subscribe(value => {this.tasks = value; });
this.form = new FormGroup({
timestampstart: new FormControl(this.appointment.timestampstart, Validators.required),
daystart: new FormControl(this.appointment.timestampstart, Validators.required),
location: new FormControl(this.appointment.location, Validators.required),
description: new FormControl(this.appointment.description, Validators.required),
paid: new FormControl(false, Validators.required),
serviceType: new FormControl(this.appointment.serviceTypeId, Validators.required),
client: new FormControl(this.appointment.clientId, Validators.required),
assignedUser: new FormControl(this.appointment.assignedUserId, Validators.required),
});
}
onSubmit(event) {
event.preventDefault();
this.formSubmitted = true;
if (this.form.valid) {
this.form.disable();
this.appointment.timestampstart = this.form.get('timestampstart').value;
this.appointment.location = this.form.get('location').value;
this.appointment.description = this.form.get('description').value;
this.appointment.paid = this.form.get('paid').value;
this.appointment.serviceTypeId = this.form.get('serviceType').value;
this.appointment.clientId = this.form.get('client').value;
this.appointment.assignedUserId = this.form.get('assignedUser').value;
this.appointmentService.createNewAppointment(this.appointment)
.subscribe(value => { this.router.navigate([`/dashboard/appointment/${value.id}/edit`]); });
} else {
this.webLogger.error('The form is invalid, please check the values');
}
}
selectTask($event: Event) {
this.task = Number(this.form.get('serviceType').value);
this.availabilityService.getAvailabilityForTask(this.task).subscribe(value => {
this.availability = value;
});
}
setTime($event: Event) {
this.availablemoments = [];
const dayAvailability: Availability[] = this.availability.filter(
s => s.timestampstart.isSame(moment(this.form.get('daystart').value), 'day'));
const currentDate = dayAvailability.reduce((prev, curr) => prev.timestampstart < curr.timestampstart ? prev : curr).timestampstart;
dayAvailability.forEach(value => {
while (value.timestampend.isAfter(currentDate)) {
if (!this.availablemoments.includes(moment(currentDate))) {
this.availablemoments.push(moment(currentDate));
}
currentDate.add(30, 'minutes');
}
});
}
}
this.availability is a list of Availability objects which include start and end moments
I expect the second console log to return the same as the first console log.
UPDATE:
The Availability class looks like this:
export class Availability {
id: number;
timestampstart: Moment;
timestampend: Moment;
location: string;
description: string;
paid: boolean;
payment: Invoice;
serviceType: Task;
client: Client;
assignedUser: User;
static serialize(data: any): Availability {
const user: Availability = Object.assign(new this(), data);
if (data.hasOwnProperty('timestampstart')) {
user.timestampstart = moment(data.timestampstart);
}
if (data.hasOwnProperty('timestampend')) {
user.timestampend = moment(data.timestampend);
}
if (data.hasOwnProperty('serviceType')) {
user.serviceType = Task.serialize(data.serviceType);
}
if (data.hasOwnProperty('client')) {
user.client = Client.serialize(data.client);
}
if (data.hasOwnProperty('assignedUser')) {
user.assignedUser = User.serialize(data.assignedUser);
}
return user;
}
}
I figured it out, since I was using Angular, I assigned the formControlName to the wrong HTML tag (the option tag instead of the select) causing the get of the value to return a value that I wasn't expecting. I've updated the main post to show the entire code.
I am trying to create a custom form validator, but I keep getting this error Cannot read property 'value' of null. I have been really struggling with is issue for days now and here is my FormGroup:
this.step1Form = this.fb.group({
username: new FormControl('', {
validators: [Validators.required,
Validators.minLength(4),
this.checkUsername.bind(this)],
}),
email: new FormControl('', {
validators: [Validators.required,
Validators.pattern("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")],
}),
password: new FormControl('', {
validators: [Validators.required,
Validators.minLength(8)],
}),
repeat: new FormControl('', {
validators: [Validators.required,
Validators.minLength(8)],
}),
});
Here is my checkUsername method
checkUsername(g: FormGroup) {
return this.dataService.checkUsername(g.get('username').value).pipe(map(data => {
return typeof data["error"] != 'undefined' ? null : {'taken': true};
}));
}
and here is the checkUsername method in this.dataService
checkUsername(username):Observable<any> {
return this.http.get(globals.baseUrl+'/user/' + username, {headers:this.utilService.getHeadersJson()}).map(this.utilService.map);
}
And I get the error:
Cannot read property 'value' of null
on this line
return this.dataService.checkUsername(g.get('username').value).pipe(map(data => {
What am I doing wrong? I have tried without the pipe like so
checkUsername(g: FormGroup) {
return this.dataService.checkUsername(g.get('username').value).subscribe(data => {
return typeof data["error"] != 'undefined' ? null : {'taken': true};
});
}
but that didnt work, I have also tried my validator in different positions like so:
this.step1Form = this.fb.group({
username: new FormControl('', {
validators: [Validators.required,
Validators.minLength(4)],
}),
email: new FormControl('', {
validators: [Validators.required,
Validators.pattern("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+#[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")],
}),
password: new FormControl('', {
validators: [Validators.required,
Validators.minLength(8)],
}),
repeat: new FormControl('', {
validators: [Validators.required,
Validators.minLength(8)],
}),
}, { validator: this.checkUsername.bind(this)});
Also didnt work.
PLEASE HELP!
I have also tried this:
username: new FormControl('', {
validators: [Validators.required,
Validators.minLength(4)],
asyncValidator: [this.checkUsername.bind(this)],
}),
got this error:
Argument of type '{ validators: ValidatorFn[]; asyncValidator: any[];
}' is not assignable to parameter of type 'ValidatorFn |
AbstractControlOptions | ValidatorFn[]'. Object literal may only
specify known properties, and 'asyncValidator' does not exist in type
'ValidatorFn | AbstractControlOptions | ValidatorFn[]'.
Angular reactive forms call the validators immediately and often. The cross-field validator on your form group will begin to validate as each child control is instantiated by the framework.
To prevent it from throwing premature validation errors, you'll probably want to ensure that the control in question has been instantiated, like so:
checkUsername(g: FormGroup) {
if (!g || !g.get('username')) {
return null;
}
return this.dataService.checkUsername(g.get('username').value).subscribe(data => {
return typeof data["error"] != 'undefined' ? null : {'taken': true};
});
}
Your 2nd attempt seems like a better direction to go, however, because it's an asynchronous validator for the 'username' control, but you have a typo in the asyncValidators property (missing the 's'). You also likely don't need to .bind(this) if you're not referencing "this" in the function.
username: new FormControl('', {
validators: [Validators.required, Validators.minLength(4)],
asyncValidators: [this.checkUsername]
})
Then your validator is just handling a single form control instead of the whole group, something like this:
checkUsername(c: FormControl) {
if (!c) { return null; }
return this.dataService.checkUsername(c.value).subscribe(data => {
return typeof data["error"] != 'undefined' ? null : {'taken': true};
});
}
I'm learning Angular testing and so far it has been pretty frustrating. I wanted to test my signup component, but it gave me the following error.
Expected spy AuthenticationService.checkUsername to have been
called.
This is the component I am trying to test. I am currently testing whether the method checkUniqueUsername() works as planned. When I call the message I expect the property uniqueUsernameMessage to contain the string 'already exists' when the authService.checkUsername() method returns {obj: true}
Unfortunately it returns the above error and for the value of the property uniqueUsernameMessage it returns undefined instead of the expected .contains(already exists).
Can someone help me figure out where I went wrong?
Signup.component.ts
#Component({
selector: 'app-sign-up',
templateUrl: 'sign-up.component.html',
styleUrls: ['sign-up.component.css']
})
export class SignUpComponent implements OnInit {
mySignupForm: FormGroup;
countries = countries;
uniqueUsernameMessage;
uniqueEmailMessage;
formSubmitted = false;
#Output() closeSubmenu = new EventEmitter();
constructor(
private authService: AuthenticationService,
private route: Router,
private navigationService: NavigationService){}
ngOnInit() {
this.mySignupForm = new FormGroup({
firstName: new FormControl(null, Validators.required),
lastName: new FormControl(null, Validators.required),
username: new FormControl(null, [Validators.required, Validators.minLength(5), Validators.maxLength(15)]),
birthDate: new FormControl(null, Validators.required),
password1: new FormControl(null, [Validators.required, Validators.minLength(6), Validators.maxLength(15)]),
password2: new FormControl(null, Validators.required),
email: new FormControl(null, [Validators.required, Validators.email]),
country: new FormControl(null, Validators.required),
house: new FormControl(null, Validators.required)
})
}
checkUniqueUsername() {
if ((this.mySignupForm.value.username >= 5 && this.mySignupForm.value.username <= 15) || null ){
this.authService.checkUsername(this.username.value)
.pipe(debounceTime(500))
.subscribe((result: any) => {
if (result.obj) {
console.log('result', result);
this.uniqueUsernameMessage = "This username already exists. Please pick another one."
} else {
this.uniqueUsernameMessage = null;
}
})
}
}
}
signup.component.spec.ts
describe('signup', () => {
let component: SignUpComponent;
let fixture: ComponentFixture<SignUpComponent>;
let authService;
beforeEach(async(() => {
const authServiceSpy = jasmine.createSpyObj('AuthenticationService', ['checkUsername', 'checkEmailUniqueness', 'signup']);
TestBed.configureTestingModule({
declarations: [SignUpComponent],
providers: [
{
provide: AuthenticationService,
useValue: authServiceSpy
},
NavigationService
],
imports: [
RouterTestingModule],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
authService = TestBed.get(AuthenticationService);
}));
beforeEach(() => {
fixture = TestBed.createComponent(SignUpComponent);
component = fixture.componentInstance;
// fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should warn user if username already exists', fakeAsync(() => {
const fakeUsername = "testUsername"
authService.checkUsername.and.returnValue(of({obj: true}));
fixture.detectChanges();
component.mySignupForm.value.username = fakeUsername;
fixture.detectChanges();
component.checkUniqueUsername();
tick(500);
expect(authService.checkUsername).toHaveBeenCalled();
expect(component.uniqueUsernameMessage).toContain('already exists');
})
)
});
This line:
component.mySignupForm.value.username = fakeUsername;
does not change the value of the FormControl. The username value is still null. Thus, when you check its validity, the condition inside if is false.
Reactive form is immutable. If you want to set new value for any FormControl, it should be done using .setValue().
Like this:
component.mySignupForm.get('username').setValue(fakeUsername);
Hope this helps :)
I am using Ionic 2 with Angular 2 beta 11.
I have a custom validator that works perfectly. However, I now need to add another custom validation method, but can't seem to work out how to pass a parameter to it.
As you can see below, I have a custom validation function:
ValidationService.userNameExists(control, this.employeeService)
'control' does not exist
How do I pass the control (FormControl) to the userNameExists function? Or, I need to pass the field value (value of username).
Thanks
register.ts
constructor(private nav: NavController, private builder: FormBuilder, employeeService: EmployeeService) {
this.employeeService = employeeService;
this.registerForm = builder.group({
'username': ['', [Validators.required, Validators.minLength(5), Validators.maxLength(55), ValidationService.userNameValidator, ValidationService.userNameExists(control, this.employeeService)]],
'password1': ['', [Validators.required, Validators.minLength(5), Validators.maxLength(45), ValidationService.passwordValidator]],
'password2': ['', [Validators.required, Validators.minLength(5), Validators.maxLength(45), ValidationService.passwordValidator]]
}, { 'validator': ValidationService.matchPassword('password1', 'password2') }
);
}
'control' does not exist
ValidationService.ts
public static userNameExists(control: FormControl, employeeService: EmployeeService): any {
return (control: FormControl) => {
let userNameInput = control.value;
console.log('userNameExists: '+control.value);
let promise: Promise<EmployeeModel> = employeeService.getEmployeByUserName(userNameInput);
promise.then((employeeModel: EmployeeModel) => {
if (employeeModel.userName === userNameInput) {
return { userNameExists: true };
} else {
return null;
}
});
}
}
Remember that when you are instantiating FormControl/FormGroup, you are passing the validation function in, not calling it. Change your username declaration as following:
'username': ['',
[Validators.required,
Validators.minLength(5),
Validators.maxLength(55),
ValidationService.userNameValidator,
(control) => ValidationService.userNameExists(control, this.employeeService)]]