Search code examples
angulartypescriptangular-formsangular-formbuilder

Angular *ngFor over FormGroup in Template - Cannot find a differ supporting object error


I'm trying to loop over a FormGroup of FormControls in the template. I want to display a checkbox for each. This is how my component.ts looks like

tags = ["vegan","vegetarian","meat","fruit","vegetable","seafood","fish","nut"];

get tagsForForm() {
    return this.foodForm.get('tags') as FormGroup;
}

createTagsForForm(): {[key: string]: any} {
    let tagsForForm = {};
    for (let i = 0; i < this.tags.length; i++){
        tagsForForm[this.tags[i]]=false;
    }
    return tagsForForm;
}

ngOnInit(){
    this.foodForm = this.fb.group({
        foodName: [''],
        tags: this.fb.group(this.createTagsForForm())
    });
}

My template.html looks like this:

<section formGroupName="tags">
    <h2>Tags</h2>
    <input *ngFor="let tag of tagsForForm" [formControlName]="tag" type="checkbox">
</section>

In the console I'm getting this error:

"Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays."

Is it not possible to loop over a FormGroup in the template or is only something missing?


Solution

  • You need to use keyvalue pipe to loop over objects. Try the following

    <input *ngFor="let tag of tagsForForm | keyvalue" [formControlName]="tag.value" type="checkbox">
    

    The value of the property is available in tag.value and the key of the property is available in tag.key.

    Update

    It appears this.foodForm.get('tags') as FormGroup doesn't return the correct object to loop over. You could try to replace it with this.foodForm.controls['tags']['controls'];. Try the following

    Controller

    export class AppComponent  {
      foodForm: FormGroup;
      tags = ["vegan", "vegetarian", "meat", "fruit", "vegetable", "seafood", "fish","nut"];
    
      get tagsForForm() {
        return this.foodForm.controls['tags']['controls'];
      }
    
      constructor(private fb: FormBuilder) {}
    
      createTagsForForm(): {[key: string]: any} {
        let tagsForForm = {};
        for (let i = 0; i < this.tags.length; i++){
          tagsForForm[this.tags[i]] = false;
        }
        return tagsForForm;
      }
    
      ngOnInit() {
        this.foodForm = this.fb.group({
          foodName: [''],
          tags: this.fb.group(this.createTagsForForm())
        });
      }
    }
    

    Template

    <form [formGroup]="foodForm">
        <label>Name: </label>
        <input formControlName="foodName" />
        <section [formGroup]="foodForm.controls.tags">
        <ng-container *ngFor="let tag of tagsForForm | keyvalue">
          <label>{{ tag.key | titlecase }}</label>
          <input [formControlName]="tag.key" type="checkbox">
          <br>
        </ng-container>
      </section>
    </form>
    

    Alternatively if you're generating the nested form group using a member variable (tags here), you could loop over it to avoid the getter and some additional overhead. The following would also work

    <form [formGroup]="foodForm">
        <label>Name: </label>
        <input formControlName="foodName" />
        <section [formGroup]="foodForm.controls.tags">
        <ng-container *ngFor="let tag of tags">
          <label>{{ tag | titlecase }}</label>
          <input [formControlName]="tag" type="checkbox">
          <br>
        </ng-container>
      </section>
    </form>
    

    Working example: Stackblitz