2

I am having a reactive form with the dynamic input field. I generated these fields with FormArray. Now, I am trying to prepopulate the form with data on the edit screen but couldn't able to do. I tried to populate using setControl method I got this error Cannot find control with path: 'books -> 0 -> name'

These are the sources I took for reference

  1. https://alligator.io/angular/reactive-forms-formarray-dynamic-fields/
  2. https://stackoverflow.com/a/44988197/9715025
  3. https://stackoverflow.com/a/42599327/9715025

Component

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, FormArray } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface ServerResponse {
  books: any;
}

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  formGroup: FormGroup;
  books: FormArray;

  constructor(
    private http: HttpClient,
    private formBuilder: FormBuilder,
  ) {

  }

  createForm() {
    this.formGroup = this.formBuilder.group({
      books: this.formBuilder.array([this.createBookForm()]),
    });
  }

  createBookForm() {
    return this.formBuilder.group({
      name: [],
      author: [],
    });
  }

  addBook() {
    this.books = this.formGroup.get('books') as FormArray;
    this.books.push(this.createBookForm());
  }

  getData(): Observable<ServerResponse> {
    return this.http.get<ServerResponse>('https://demo0331989.mockable.io/library/data/book');
  }

  populateData() {
    this.getData().subscribe(data => {
      this.formGroup.setControl('books', this.formBuilder.array(data.books || []));
    });
  }

  ngOnInit() {
    this.createForm();
    this.populateData();
  }
}

HTML

<form [formGroup]="formGroup">
  <div formArrayName="books" *ngFor="let book of formGroup.get('books')['controls']; let i = index;">
    <div class="row" [formGroupName]="i">
      <input type="text" autocomplete="off" placeholder="*Name"
      formControlName="name">
      <input type="text" autocomplete="off" placeholder="*Author"
      formControlName="author">
      <button type="button" class="btn btn-success" (click)="addBook();">Add</button>
    </div>
  </div>
</form>

I am also sharing the code in Stackblitz. Sample/mock data to be prepopulated. I am not sure what I'm missing here!

HenonoaH
  • 399
  • 2
  • 10

3 Answers3

3

disclamer this answer is complementary with the @bryan60 answer,

I like my function createBookForm has as argument "data", so

createBookForm(data:any) {
    data=data||{name:null,author:null}
    return this.formBuilder.group({
      name: data.name,
      author: data.author,
    });
  }

This allow us write

createBookForm(null) //create a FormGroup with name and author and values null
//or 
createBookForm({name:"Linus Administration",author:"Roger"}) //the formgroup has values

Our function populateData becomes more easy

  populateData() {
    this.getData().subscribe(data => {
      const books = this.formGroup.get('books') as FormArray;
      books.clear();
      data.books.forEach(b => {
        books.push(this.createBookForm(b))
      });
    });
  }

NOTE: I can't see the necesity of declare a formGroup with a unique propertie that is a FormArray, see my answer among others in stackoverflow question

Eliseo
  • 50,109
  • 4
  • 29
  • 67
2

when populating a form array, you need to populate it with filled form groups, so it'd look something like this:

populateData() {
  this.getData().subscribe(data => {
    const books = this.formGroup.get('books') as FormArray; // get the array
    books.clear(); // clear the array 
    data.books.forEach(b => { // iterate data
      let fg = this.createBookForm(); // create the group
      fg.reset(b); // fill the data
      books.push(fg); // add to array
    });
    this.addBook(); // add blank form to end if desired
  });
}

the clearing the form part / adding the blank at the end may or may not be needed.

here is a fixed blitz: https://stackblitz.com/edit/angular-9ts4yv?file=src/app/app.component.ts

bryan60
  • 28,215
  • 4
  • 48
  • 65
1

I did a fork from your stackblitz example, so take a look. When you want to pre populate your form you need to create BookForm and pass it in setControl, not just array.

N.Tasikj
  • 366
  • 1
  • 4