Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to validate reactive forms with nested form groups?

I'm working on creating a nested form group as shown in https://plnkr.co/edit/c93yFe2r83cvjiRvl6Uj?p=preview.

This is a fork of example from https://angular.io/docs/ts/latest/cookbook/form-validation.html#!#reactive

The example in Angular shows using validationMessages when the form does not have any nest groups. I'm not sure how to extend this when I have nested form groups. Following are sample form and validation messages I would create.

  buildForm(): void {
    this.heroForm = this.fb.group({
      'name': [this.hero.name, [
          Validators.required,
          Validators.minLength(4),
          Validators.maxLength(24),
          forbiddenNameValidator(/bob/i)
        ]
      ],
      'address': this.fb.group({
        'city': [''],
        'state': ['']
      }),
      'alterEgo': [this.hero.alterEgo],
      'power':    [this.hero.power, Validators.required]
    });

    this.heroForm.valueChanges
      .subscribe(data => this.onValueChanged(data));

    this.onValueChanged(); // (re)set validation messages now
  }

Expected formErrors and validationMessages

  formErrors = {
    'name': '',
    'power': '',
    'address': {
      'state': '',
      'city': ''
    }
  };

  validationMessages = {
    'name': {
      'required':      'Name is required.',
      'minlength':     'Name must be at least 4 characters long.',
      'maxlength':     'Name cannot be more than 24 characters long.',
      'forbiddenName': 'Someone named "Bob" cannot be a hero.'
    },
    'power': {
      'required': 'Power is required.'
    },
    'address': {
      'city': {
        'required':      'City is required.',
        'minlength':     'City must be at least 4 characters long.',
      },
      'state': {
        'required':      'State is required.',
        'minlength':     'State must be at least 4 characters long.',
      }
    }
  };

Following is the onValueChanged

  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] + ' ';
        }
      }
    }
  }

How do I update the above onValueChanged function to support nested form groups validation ?

I'm doing above because I have a wizard form and I want to validate nested form group before moving to the next step in the wizard. Each wizard step contains a nested form group. The current wizard step must be valid before going to next. I don't want to put the content of wizard into multiple components because all the data must from the wizard must be submitted together at the end to a remote service. So I don't see value in complicated by having multiple components.

like image 915
TechCrunch Avatar asked Mar 22 '17 02:03

TechCrunch


1 Answers

I know that this is an old story, but I was trying to get the validateAllFormGroups(formGroup: FormGroup) synchronously and I didn't find any answer somewhere so I implemented my approach:

function validateAllFormGroups(formGroup: FormGroup): boolean {
    let hasErrors = false;

    Object.keys(formGroup.controls).forEach((field) => {
      const control = formGroup.get(field);

      hasErrors =
        control instanceof FormGroup
          ? hasErrors || !validateAllFormGroups(control)
          : hasErrors || !!control?.errors;
    });

    return !hasErrors;
}

You can adapt it to get the results that the logic of your application needs, for me a boolean value was enough.

Any optimization or suggestion would be welcome

like image 92
StPaulis Avatar answered Oct 10 '22 18:10

StPaulis