Q:

Angular 2 Form validation for dynamic fields

I am working on an Angular 4.X project and I am creating some HTML input fields (mostly of type text) on button click. The creation of Input Boxes dynamically is working fine but I am not able to implement validation to those fields. I am getting the following error.
Cannot read property 'invalid' of null(…)
I have created a plunk for the same. Following is the link for the plunk that I have created --
https://plnkr.co/edit/PCFD43GK91zo2ivQ9lf7?p=preview
For easy reference please find the code below --
//Root app component
import {Component, NgModule} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import { FormBuilder, FormGroup, Validators, FormArray, FormControl } from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'my-app',
  template: `<hr>
              <div>  
                <form [formGroup]="orderForm" (ngSubmit)="OnSubmit(orderForm.value)">
                  <div>
                    <input type="text" formControlName="customerName"/>
                    <input type="text" formControlName="email"/>
                  </div>
                  <div formArrayName="items" *ngFor="let item of items.controls; let i = index;">
                    <div [formGroupName]="i">
                      <input type="text" formControlName="name" placeholder="Item name"/>
                      <small *ngIf="IsValidField('name')" class="text-danger">
                        Name is required
                      </small>
                      <input type="text" formControlName="description" placeholder="Item description"/>
                      <small *ngIf="IsValidField('description')" class="text-danger">
                        Description is required
                      </small>
                      <input type="text" formControlName="price" placeholder="Item price"/>
                      <small *ngIf="IsValidField('price')" class="text-danger">
                        Price is required
                      </small>
                    </div>
                    Chosen name: {{ orderForm.controls.items.controls[i].controls.name.value }}
                  </div>
                  <button type="submit">Save</button>
                  <button type="button" (click)="addItem()">Add More</button>
                </form>
              <div>`,
})

export class App {

  constructor(private formBuilder: FormBuilder) {  }

  public orderForm: FormGroup;

  ngOnInit() {
      this.orderForm = this.formBuilder.group({
        customerName: '',
        email: '',
        items: this.formBuilder.array([ this.createItem()])
      });
  }

 createItem(): FormGroup {
    return this.formBuilder.group({
      name: ['',[Validators.required,Validators.maxLength(10)]],
      description: '',
      price: ['',[Validators.required,Validators.pattern("[(0-9)*]")]]
    });
  }

   get items(): FormArray {
    return this.orderForm.get('items') as FormArray;
   };

  addItem(): void {
  this.items.push(this.createItem());
  }

  public OnSubmit(formValue: any) {
        console.log(JSON.stringify(formValue));
    }

    public IsValidField(field: string) {
        return (this.orderForm.get(field).invalid && this.orderForm.get(field).touched) || (this.orderForm.get(field).invalid && this.orderForm);
    }

}

@NgModule({
  imports: [ BrowserModule, FormsModule, ReactiveFormsModule ],
  declarations: [ App ],
  bootstrap: [ App ]
})
export class AppModule {}

Any help would be appreciated.

A:

You're searching for dynamically added form fields inside orderForm, which isn't available there. You should query form correctly before accessing its value. I draw below figure which will help you to understand how the dynamically created form architecture.
orderForm (FormGroup)
  |
  - Items (FormArray)
    |
     - 0 (FormGroup)
       - name 
       - description
       - price
    |
     - 1 (FormGroup)
       - name
       - description
       - price
    |
    |.........

So IsValidField function should get the index of formArray element, and field name. By which you can easily query form element.
public IsValidField(i: number, field: any) {
    var f = this.orderForm
            .get('items') //retrieve items FormArray
            .get(i.toString()) //retrieve items FormGroup
            .get(field); //retrieve items form field
    return (f.invalid && f.touched) || (f.invalid && this.orderForm);
}

Then change IsValidField function call on *ngIf accordingly.
*ngIf="IsValidField(i, 'name')"
*ngIf="IsValidField(i, 'description')"
*ngIf="IsValidField(i, 'price')"

Demo Plunker

