Picture of Brian Love wearing black against a dark wall in Portland, OR.

Brian Love

Dynamic FormGroup Custom Validation in Angular2

In this post I will show you how to create nested FormGroups to create a custom validator. This works well for related form inputs, such as checkboxes. If you are new to Angular 2 forms, check out Thoughtram’s post on reactive forms - it’s a good read.

Getting Started

To review Angular 2 has both model-driven forms as well as template-driven forms. I am using the model-driven reactive form. As such, I have imported the necessary classes from the @angular/forms module.

import {
  FormBuilder,
  FormControl,
  FormGroup,
  ReactiveFormsModule,
  Validators,
} from '@angular/forms';

I have imported the FormBuilder, FormControl, FormGroup and Validators classes. A quick overview of these:

Form Template

Before we get to the form template, I want to create an array of days of the week and store this as a public variable in my component. This is only because I am going to use the name and values when creating the checkboxes in my form. You could simply create the necessary input[type="checkbox"] elements in your template instead.

interface IDay {
  name: string;
  value: string;
}

@Component({})
export class UpdateComponent implements ngOnInit {
  public days: IDay[] = [
    {value: "sunday", name:"Sunday"},
    {value: "monday", name:"Monday"},
    {value: "tuesday", name:"Tuesday"},
    {value: "wednesday", name:"Wednesday"},
    {value: "thursday", name:"Thursday"},
    {value: "friday", name:"Friday"},
    {value: "saturday", name:"Saturday"},
  ];
}

Now, let’s look at the form template:

<form [formGroup]="detailsForm" novalidate>
  <input type="text" [formControl]="detailsForm.get('name')" [class.invalid]="!detailsForm.controls.name.valid">
  <div formGroupName="days">
    <p *ngFor="let day of days">
      <input type="checkbox" [formControlName]="day.value" [class.invalid]="!detailsForm.controls.days.valid">
      <label>{{day.name}}</label>
    </div>
  </div>
</form>

A couple of things to note:

Building a FormGroup

Back to our controller, we now need to create our FormGroup variable named detailsForm. The detailsForm will contain all of the details about the FormControls, any nested FormGroups, and their FormControls.

@Component({})
export class UpdateComponent implements ngOnInit {
  public detailsForm: FormGroup;

  constructor(private formBuilder: FormBuilder) { }

  public ngOnInit() {
    //create daysFormGroup using FormGroup long-hand syntax
    //this is so I can create a dynamic form from the array of IDay objects
    let daysFormGroup: FormGroup = new FormGroup({});
    for (let day of this.days) {
      let control: FormControl = new FormControl(day.value, Validators.required);
      daysFormGroup.addControl(day.value, control);
    }

    //create detailsForm FormGroup using FormBuilder's short-hand syntax
    this.detailsForm = this.formBuilder.group({
      name: ["", Validators.required],
      days: daysFormGroup
    });
  }
}

Let’s walk through this:

Create Custom Validator for FormGroup

Now we can create our custom validator for the daysOfWeek FormGroup. I am going to create a new private method in my UpdateComponent named validateDays(). This method will be passed the FormGroup.

/**
 * Validate the days checkboxes. At least one must be selected.
 *
 * @class UpdateComponent
 * @method validateDays
 * @return {null|Object} Null if valid.
 */
private validateDays(formGroup: FormGroup) {
  for (let key in formGroup.controls) {
    if (formGroup.controls.hasOwnProperty(key)) {
      let control: FormControl = <FormControl>formGroup.controls[key];
      if (control.value) {
        return null;
      }
    }
  }

  return {
    validateDays: {
      valid: false
    }
  };
}

The validateDays method loops over each control in the FormGroup and checks if the control has a value. If any of the control’s have a value, then the method returns null - the FormGroup is valid. Otherwise, we return an object specifying that the validateDays validator is not valid.

The last thing we need to do is tell our daysFormGroup instance that we have a validator. So, let’s modify our ngOnInit() method:

@Component({})
export class UpdateComponent implements ngOnInit {
  public ngOnInit() {
    //create daysFormGroup using FormGroup long-hand syntax
    //this is so I can create a dynamic form from the array of IDay objects
    let daysFormGroup: FormGroup = new FormGroup({}, (formGroup: FormGroup) => {
      return this.validateDays(formGroup);
    });
  }
}

I am now specifying the validator function for the FormGroup. Unfortunately, it doesn’t look like there is a way to set the validator after creating the FormGroup instance.

Using FormBuilder

You can also set the validator when using FormBuilder using the following syntax:

@Component({})
export class UpdateComponent implements ngOnInit {
  public ngOnInit() {
    //create detailsForm FormGroup using FormBuilder's short-hand syntax
    this.detailsForm = this.formBuilder.group({
      name: ["", Validators.required],
      days: this.formBuilder.group({
        sunday: false,
        monday: false,
        tuesday: false,
        wednesday: false,
        thursday: false,
        friday: false,
        saturday: false
      }, {
        validator: (formGroup: FormGroup) => {
          return this.validateDays(formGroup);
        }
      })
    });
  }
}

In this example I am not dynamically creating my FormGroup, rather I have specified each control in my form (each day of the week). I then pass in the extras object as the second argument, which has a property named validator, which is my validator function. I am using TypeScript’s fat arrow function to bind the scope of the anonymous function to the class’s this scope.