angulartypescriptangular-materialangular-reactive-formsformarray

How to get data from FormArray in Angular


I want to define a question which has some answers. Here is my code:

  formGroup!:FormGroup;
  answerFormGroup:FormGroup=this.formBuilder.group({
    text: ['d', Validators.required],
    value: [0, Validators.required]
  });
  ngOnInit() {
    this.formGroup = this.formBuilder.group({
      question:['',Validators.required],
      questionType: [1,Validators.required],
      answers: new FormArray([])
    });
   this.questionTypes=this.localDataService.questionType;
   this.addAnswer();
  }

   // convenience getters for easy access to form fields
   get answers() {
    return this.formGroup.controls["answers"] as FormArray;
  }

  addAnswer(){
    this.answers.push(this.formBuilder.group({
      text: ['d', Validators.required],
      value: [0]
    }));
  }

  submit(){
       console.log(JSON.stringify(this.formGroup.value));
  }

But when I submit my form the answer values are the default values and it does not change. In the form I changed the default value "d" to "b", and it is still "d". Here is the result:

{
  "question": "question1",
  "questionType": 1,
  "answers": [
    {
      "text": "d",
      "value": 0
    }
  ]
}

Here is the template:

<form class="question-card" [formGroup]="formGroup">
    <mat-form-field>
        <mat-label>Question</mat-label>
        <input matInput formControlName="question" placeholder="Question">
    </mat-form-field>
    <mat-form-field>
        <mat-label>Type</mat-label>
        <mat-select formControlName="questionTypeControl" (selectionChange)="updateQuestionType($event)">
            @for (item of questionTypes; track item) {
                <mat-option [value]="item.id">
                    <mat-icon>{{item.icon}}</mat-icon> {{item.text}}
                </mat-option>
            }
        </mat-select>
    </mat-form-field>  
    <div [formGroup]="answerFormGroup">
    @for(item of answers.controls; track item){
        <mat-form-field>
            <mat-label>Value</mat-label>
            <input matInput type="text" formControlName="value">
        </mat-form-field>
        <mat-icon class="delete-btn" (click)="deleteAnswer($event)">delete_forever</mat-icon>
        <mat-form-field>
            <mat-label>Text</mat-label>
            <input matInput type="text" formControlName="text">
        </mat-form-field>
    }
    </div>
    <button mat-mini-fab (click)="addAnswer()">
            <mat-icon>add</mat-icon>
    </button>
    <button type="button" (click)="submit()">Submit</button>
</form>

Solution

  • You are updating the text control in the answerFormGroup FormGroup but not formGroup. From here:

    <div [formGroup]="answerFormGroup">
      ...
    </div>
    

    In the formGroup template, you need to have the formArray control for the answers.

    <form class="question-card" [formGroup]="formGroup">
    
      ...
    
      <ng-container formArrayName="answers">
        @for (item of answers.controls; track item; let i = $index) {
    
          <div [formGroupName]="i">
            <mat-form-field>
              <mat-label>Value</mat-label>
              <input matInput type="text" formControlName="value" />
            </mat-form-field>
            <mat-icon class="delete-btn" (click)="deleteAnswer($event)"
              >delete_forever</mat-icon
            >
            <mat-form-field>
              <mat-label>Text</mat-label>
              <input matInput type="text" formControlName="text" />
            </mat-form-field>
          </div>
        }
      </ng-container>
    
      ...
    
    </form>
    

    The @for syntax is a new feature in Angular 17 and quite new to me. If you are looking for the old way:

    <ng-container formArrayName="answers">
      <ng-container *ngFor="let answer of answers.controls; let i = index">
        <div [formGroupName]="i"> 
        
        <mat-form-field>
          <mat-label>Value</mat-label>
          <input matInput type="text" formControlName="value" />
        </mat-form-field>
        <mat-icon class="delete-btn" (click)="deleteAnswer($event)"
          >delete_forever</mat-icon
        >
        <mat-form-field>
          <mat-label>Text</mat-label>
          <input matInput type="text" formControlName="text" />
        </mat-form-field>
      </div>
        
      </ng-container>
    </ng-container>
    

    Demo @ StackBlitz

    Note that you are missing questionTypeControl control in the formGroup.