Angular 2 - Reactive Validation Form

My goal is to put all my validation messages in a component instead of an html file

I have a page for registration and below are the fields:

public buildRegisterForm() { this.userForm = this.fb.group({ firstName: ['', [Validators.required, Validators.minLength(3)]], lastName: ['', [Validators.required, Validators.maxLength(50)]], emailGroup: this.fb.group({ email: ['', [Validators.required, Validators.pattern(this.emailPattern)]], retypeEmail: ['', Validators.required], }, { validator: formMatcherValidator('email', 'retypeEmail') }), passwordGroup: this.fb.group({ password: ['', [Validators.required, strongPasswordValidator()]], retypePassword: ['', Validators.required], }, { validator: formMatcherValidator('password', 'retypePassword')}), }); } 

I follow this link guide to achieve what I want to put all my validation messages in a component file instead of an html file.

 export const validationMessages = { 'firstName': { 'required': 'Your first name is required.', 'minlength': 'Your first name must be at least 3 characters long.' }, 'lastName': { 'required': 'Your last name is required.', 'minlength': 'Your last name must be less than 50 characters long.' }, 'emailGroup': { 'email': { 'required': 'Your email is required', 'pattern': 'Your login email does not seem to be a valid email address.' }, 'retypeEmail': { 'required': 'Your retype email is required', 'match': 'The email provided do not match.' }, }, 'passwordGroup':{ 'password': { 'required': 'Your password is required', 'strongPassword': 'Your password must be between 8 - 15 characters and must contain at least three of the following: upper case letter, lower case letter, number, symbol.' }, 'retypePassword': { 'required': 'Your retype password is required', 'match': 'The password provided do not match.' } } 


onValueChanged method

 private onValueChanged(data?: any) { if (!this.userForm) { return; } const form = this.userForm; // tslint:disable-next-line:forin for (const field in this.formErrors) { // clear previous error message (if any) this.formErrors[field] = ''; let control = form.get(field); // console.log("control", control.dirty); console.log("controlEmail", control); if (control && (control.dirty || control.touched) && control.invalid) { let messages = validationMessages[field]; // tslint:disable-next-line:forin for (const key in control.errors) { this.formErrors[field] += messages[key] + ' '; } } } } 

And this method does not work when I have multi formBuider Group or nested object. Any tips for this 1? similar to this How do I test reactive forms with nested form groups?

+7
angular
source share
3 answers

As I see this, you need to create a nested loop inside the onValueChanged(data) method. Since you have quite a few nested groups, I am not going to replicate this. But the nested loop is shared, so it works for all of your groups. But here is an example with one nested group, and not with several. I use an example of heroes.

The nested name of the group is group , and formcontrol inside is child .

formErrors , which are used in the code, must have a child inside group :

 formErrors = { 'name': '', 'power': '', 'group':{ 'child': '' } }; 

Therefore, you must remember when you add validation to the template, you need to use:

 <div *ngIf="formErrors.group.child"> {{ formErrors.group.child }} </div> 

Verification messages will not be inside group , but in the same way as other verification messages:

 validationMessages = { 'name': { 'required': 'Name is required.', }, 'power': { 'required': 'Power is required.' }, 'child': { 'required': 'Child is required.', } }; 

Finally, the modified onValueChanges :

 onValueChanged(data?: any) { if (!this.heroForm) { return; } const form = this.heroForm; // iterate toplevel of formErrors for (const field in this.formErrors) { // check if the field corresponds a formgroup (controls is present) if(form.get(field).controls ) { // if yes, iterate the inner formfields for(const subfield in form.get(field).controls) { // in this example corresponds = "child", reset the error messages this.formErrors[field][subfield] = ''; // now access the actual formfield const control = form.get(field).controls[subfield]; // validate and show appropriate error message if (control && control.dirty && !control.valid) { const messages = this.validationMessages[subfield]; for (const key in control.errors) { this.formErrors[field][subfield] += messages[key] + ' '; } } } } // does not contain a nested formgroup, so just iterate like before making changes to this method else { const control = form.get(field); this.formErrors[field] = ''; if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += messages[key] + ' '; } } } } } 

Finally, DEMO :)

Plunker

You should remember that in your case this works, but if there were nested groups inside the nested groups, this would not work, then you will need to do another loop in onValueChanges , but you do not have this problem here;)

+7
source share

You can also use the following onValueChanged original onValueChanged :

 formErrors = { 'name': '', 'power': '', 'group.child':'' }; validationMessages = { 'name': { 'required': 'Name is required.', }, 'power': { 'required': 'Power is required.' }, 'group.child': { 'required': 'Child is required.', } }; onValueChanged(data?: any) { if (!this.heroForm) { return; } const form = this.heroForm; for (const field in this.formErrors) { // clear previous error message (if any) this.formErrors[field] = ''; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += messages[key] + ' '; } } } } 
+3
source share

I want the Nehal solution to work, but I could not do it. Came with my solution after working with @ AJT_82 code. My need really was that you wanted to check the passwords as a group, and his solution did not cover it. So, I included other things that I used to create a complete solution that worked for me.

I ran into this problem while trying to do a password confirmation check in angular 4 after finding that the method I used in 2 didn't work anymore. The information about angular.io actually did not help much, since most of the information is fragmented in different areas of their documentation.

So, to clarify, this follows the angular reactive form method. I wrote this to verify passwords in an instance where I also had other verification restrictions (password must be between 4 and 24 characters, required, etc.). This method should work just as well for email confirmation with a few small tweaks.

First, to compare validators for a group, the html form must have a subgroup identified using the identifier formGroupName = "". This is in the main identifier of [formGroup]. Composite input must be inside this element labeled formGroupName. In my case, it's just a div.

 <div class="title">Hero Registration Form</div> <form [formGroup]="regForm" id="regForm" (ngSubmit)="onSubmit()"> <div class="input"> <label for="heroname">Heroname</label> <input type="text" id="heroname" class="form-control" formControlName="heroname" required /> <div *ngIf="formErrors.heroname" class="hero-reg-alert">{{ formErrors.heroname }}</div> </div> <div class="input"> <label for="email">Email</label> <input type="email" id="email" class="form-control" formControlName="email" required /> <div *ngIf="formErrors.email" class="hero-reg-alert">{{ formErrors.email }}</div> </div> <div formGroupName="password"> <div class="input"> <label for="password1">Password</label> <input type="password" id="password1" class="form-control" formControlName="password1" required /> <div *ngIf="formErrors.password.password1" class="hero-reg-alert"> {{ formErrors.password.password1 }}</div> </div> <div class="input"> <label for="password2">Re-Enter Password</label> <input type="password" id="password2" class="form-control" formControlName="password2" required /> <div *ngIf="formErrors.password.password2 || formErrors.password.password" class="hero-reg-alert">{{ formErrors.password.password }} {{ formErrors.password.password2 }}</div> </div> </div> <button type="submit" [disabled]="!regForm.valid"> <span id="right-btntxt">SUBMIT</span> </button> </form> 

You may notice that I have values ​​for formErrors as

 formErrors.password.password formErrors.password.password1 formErrors.password.password2 

They are embedded in my code so ...

  formErrors = { 'username': '', 'email': '', 'password': { 'password': '', 'password1': '', 'password2': '' } }; 

I built it in such a way that "password1" and "password2" save my Errors form for each field, and "password" contains my error in case of inconsistency (where the two entered passwords are not equal).

There could be an onValueChanged () formula. It checks if the field is an instance of FormGroup. If so, he first checks the user check for this group of fields (storing them in 'this.formErrors [field] [field]'), then proceeds to processing the subfields. In case it is not an instance of FieldGroup, the check is performed according to the example in the angular.io guideance doc.

  onValueChanged(data?: any) { if (!this.regForm) {return;} const form = this.regForm; for (const field in this.formErrors) { const formControl = form.get(field); if (formControl instanceof FormGroup) { this.formErrors[field][field] = ''; // check for custom validation on field group const control = form.get(field); // handle validation for field group if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field][field] += messages[key] + ' '; } } // handle validation for subfields for (const subfield in formControl.controls) { console.log('SUBFIELD', subfield); this.formErrors[field][subfield] = ''; const control = formControl.controls[subfield]; if (control && control.dirty && !control.valid) { const messages = this.validationMessages[subfield]; for (const key in control.errors) { this.formErrors[field][subfield] += messages[key] + ' '; } } } } // alternate validation handling for fields without subfields (AKA not in a group) else { const control = form.get(field); this.formErrors[field] = ''; if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += messages[key] + ' '; } } } } } 

By providing the FieldGroup with a subfield with its own name, we can store checks in the FieldGroup. Trying to do this with the usual onValueChange code overwrites the subfield in the line ...

  this.formErrors[field] = ''; 

Not providing storage space for the FieldGroup check either overwrites the subfield or does not process the FieldGroup.

If you need it, this is how the form is created using buildFomr ();

  buildForm(): void { this.regForm = this.formBuilder.group({ 'username': [this.registerUser.username, [ Validators.required, Validators.minLength(4), Validators.maxLength(24) ] ], 'email': [this.registerUser.email, [ Validators.email, Validators.required ] ], 'password': this.formBuilder.group({ 'password1': [this.registerUser.password, [ Validators.required, Validators.minLength(8), Validators.maxLength(24) ] ], 'password2': ['', [ Validators.required ] ] }, {validator: this.passwordMatchValidator}) }); // end buildForm this.regForm.valueChanges .subscribe(data => this.onValueChanged(data)); this.onValueChanged(); // (re)set validation messages now } 

and this is a special check function ...

  passwordMatchValidator(control: FormGroup): {[key: string]: any} { return control.get('password1').value !== control.get('password2').value ? {mismatch: true} : null; } 
+1
source share

All Articles