0

I'm having a problem validating nested forms using Reactive Forms in Angular. I had a sign up form that was working properly but after I added form groups to check the validity separately I get some errors. Those errors are because the validators works just on the main form and not the nested ones. I tried to access the value of the nested forms but I'm getting that the value of the form is undefined.

component.ts

import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators, FormControl } from "@angular/forms";

@Component({
  selector: 'my-page-sign-up',
  styleUrls: ['./sign-up.component.scss'],
  templateUrl: './sign-up.component.html'
})

export class PageSignUpComponent implements OnInit {

 myForm: FormGroup;

 constructor(private fb: FormBuilder) {
                    this.myForm = this.fb.group({
                      'emailPass': this.fb.group({
                        email: ['', [
                          Validators.required,
                          Validators.email
                        ]],
                        password: ['', Validators.required],
                        confirmPassword: ['', Validators.compose([
                            Validators.required,
                            this.isEqualPassword.bind(this)
                        ])],
                      }),
                      })
                      }

  isEqualPassword(control: FormControl): {[s: string]: boolean} {
    if (!this.myForm.controls) {
        return {passwordsNotMatch: true};

    }
    if (control.value !== this.myForm.controls['password'].value) {
        return {passwordsNotMatch: true};
    }
}
}

html

<form [formGroup]="myForm" (ngSubmit)="onSignup()">
<div formGroupName="emailPass" class="form-group">
    <md-input-container class="col-md-6 md-icon-left">
        <md-icon class="material-icons">mail_outline</md-icon>
        <input formControlName="email" mdInput #email type="email" name="email" class="form-control" placeholder="Email">
        <!--md-hint *ngIf="!myForm.get(['emailPass', 'email']).pristine && myForm.hasError('noEmail', 'email')">Invalid mail address</md-hint-->
    </md-input-container>
    <md-input-container class="col-md-6 md-icon-left no-margin-bottom">
        <md-icon class="material-icons">lock_outline</md-icon>
        <input required mdInput formControlName="password" type="password" class="form-control" id="password" name="password" placeholder="Contraseña">
    </md-input-container>
    <md-input-container class="col-md-6 md-icon-left no-margin-bottom">
        <md-icon class="material-icons">lock_outline</md-icon>
        <input required mdInput type="password" formControlName="confirmPassword" class="form-control" name="password" placeholder="Confirmar Contraseña">
    </md-input-container>
</div>
<button md-raised-button type="submit">Siguiente</button>

So, I have two questions. The first one is how do I fix the error I'm getting trying to validate password and email. The second one is to know how can I disabled the button if the nested form (emailPass form) is not valid.

Thanks for your help as always!

claudiomatiasrg
  • 578
  • 4
  • 11
  • 31

1 Answers1

2

Please check out this answer I gave for an almost similar question comparing two sibling formControls.

So based on that,

import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators, FormControl } from "@angular/forms";

function isEqualPassword(control: AbstractControl): {[s: string]: boolean} {
 if (!control.get('password') || !control.get('confirmPassword')) {
   return null; // returning validity true if either one of the controls not present - safety check on initial load
 }
 return control.get('password').value === control.get('confirmPassword').value ? null : {passwordsNotMatch: true};
}

@Component({
  selector: 'my-page-sign-up',
  styleUrls: ['./sign-up.component.scss'],
  templateUrl: './sign-up.component.html'
})

export class PageSignUpComponent implements OnInit {

 myForm: FormGroup;

 constructor(private fb: FormBuilder) {
   this.myForm = this.fb.group({
     emailPass: this.fb.group({
       email: ['', [
         Validators.required,
         Validators.email
       ]],
       passwordsCtrl: this.fb.group({
         password: ['', Validators.required],
         confirmPassword: ['', Validators.required]
         }, { validator: isEqualPassword }
       })
     })
   })
 }     
}

Then wrap your password related controls in another div or something similar (your choice) in another formGroupName to denote the newly created wrapping formGroup for these 2 controls.

<form [formGroup]="myForm" (ngSubmit)="onSignup()">
<div formGroupName="emailPass" class="form-group">
    <md-input-container class="col-md-6 md-icon-left">
        <md-icon class="material-icons">mail_outline</md-icon>
        <input formControlName="email" mdInput #email type="email" name="email" class="form-control" placeholder="Email">
        <!--md-hint *ngIf="!myForm.get(['emailPass', 'email']).pristine && myForm.hasError('noEmail', 'email')">Invalid mail address</md-hint-->
    </md-input-container>
    <div formGroupName="passwordsCtrl">
     <md-input-container class="col-md-6 md-icon-left no-margin-bottom">
         <md-icon class="material-icons">lock_outline</md-icon>
         <input required mdInput formControlName="password" type="password" class="form-control" id="password" name="password" placeholder="Contraseña">
     </md-input-container>
     <md-input-container class="col-md-6 md-icon-left no-margin-bottom">
         <md-icon class="material-icons">lock_outline</md-icon>
         <input required mdInput type="password" formControlName="confirmPassword" class="form-control" name="password" placeholder="Confirmar Contraseña">
     </md-input-container>
    </div>
</div>
<button md-raised-button type="submit">Siguiente</button>

Regarding disabling the button, your myForm and emailPass formGroups are essentially the same. If thats all you have in your form, you could as well omit either one of the two. So you can check for the form's validity using the valid flag of either myForm or the specific emailPass formGroup like this,

<button md-raised-button type="submit" [disabled]="!myForm.valid">Siguiente</button>

or

<button md-raised-button type="submit" [disabled]="!myForm.get('emailPass').valid">Siguiente</button>

Hope it helps.

amal
  • 3,140
  • 1
  • 13
  • 20
  • Hi @amal.First of all thanks for your help, but the code its not working. It seems that there are some typings errors in the code you provide. I tried to fix that but the function to equal passwords is not working. By the way, I imagine that here it should say `(!control.get('password')` instead of `(!c.get('password')`. The other problem I'm finding is that Cannot read property 'valid' of undefined of emailPass in the html – claudiomatiasrg Oct 02 '17 at 15:58
  • yes you are right about that. I missed while copying my answer from the other reference – amal Oct 02 '17 at 16:02
  • Updated my answer. It should likely fix it for you. See the last `button` code line where you refer to the `valid` property of `emailPass`. – amal Oct 02 '17 at 16:07
  • Thank you very much @amal. It worked. I already marked the question as answered, but could you help me with one more thing please? How can I make this part work? `Invalid mail address` – claudiomatiasrg Oct 02 '17 at 16:31
  • if you just want to show an generic error message for whenever `email` field is empty or not valid format, then this should do. `Invalid mail address` – amal Oct 02 '17 at 17:14
  • Thank you man. You've been helpfu l and I'm embarrased to say this, but now I'm encountering another error and it's that I'm unable to use the equalpassword validator as an ngIf directive in my HTML. I'm able to validate if the two strings are equal so the validity of the form works as expected, but the confirmPassword input changes to valid even if the passwords are not equal. – claudiomatiasrg Oct 03 '17 at 01:16
  • That is the expected behaviour as the custom validation function is defined on the `passwordsCtrl` form group that wraps the child controls `password` and `confirmPassword`. So when the validation fails it puts an error flag on that formGroup instead of either of its children controls. If you want to set validation errors on specific child controls then check out the link I gave in my answer which briefly talks about exactly that towards the end. Hope it helps. – amal Oct 03 '17 at 02:18
  • Thank you for everything Amal! I'll check it out! – claudiomatiasrg Oct 03 '17 at 03:08