Search code examples
angularangular-forms

Angular/Reactive Forms - Add or Delete an list of FormControl - Binding template (With demo)


I have a matSelect since I can select a number from 1 to 10, depending on the number I have to display the same number ofmatInput with a *ngFor. The number selected by default in the select is 1 (so we have a one matInput by default too)

<mat-form-field>
  <mat-select placeholder="Number of winners to reward" [value]="selected" (selectionChange)="selectNumber($event)">
    <mat-option *ngFor="let emailNumber of totalEmailsNumber" [value]="emailNumber">
      {{ emailNumber }}
    </mat-option>
  </mat-select>
</mat-form-field>

<div formArrayName="mails">
  <mat-form-field *ngFor="let email of form.get('mails').controls; let i = index">
    <textarea matInput [formControlName]="i"></textarea>
  </mat-form-field>
</div>

to add the default FormControl of the first MatInput I do:

this.form.controls['mails'] = this.fb.array(this.emailsNumber.map(() => new FormControl('', Validators.required)));

Which works, but I can not manage the logic to add another FormControl or to delete it if for example the user chose 10 in the select then changed it to 4, and manage the binding in the template.

Im also wondering is *ngFor="let email of form.get('mails').controls is a good practice?


Here's a Minimal StackBlitz Demo to work with


Solution

  • Give this a try:

    import { Component, OnInit } from '@angular/core';
    import { FormArray, FormBuilder, Validators } from '@angular/forms';
    
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html'
    })
    export class AppComponent implements OnInit {
      form: FormGroup;
      totalEmailsNumber: number[];
    
      constructor(private fb: FormBuilder) {
      }
    
      ngOnInit() {
        this.totalEmailsNumber = this.createCustomLengthArray(10);
        this.form = this.fb.group({
          mails: this.fb.array([])
        });
      }
    
      get mails() {
        return (<FormArray>this.form.controls['mails']);
      }
    
      selectNumber(emailNumbers) {
        const difference = this.mails.length - emailNumbers;
        difference > 0 ? this.removeMails(difference) : this.addMails(difference);
      }
    
      removeMails(difference) {
        this.createCustomLengthArray(difference)
          .forEach(item => this.mails.removeAt(this.mails.length - 1));
      }
    
      addMails(difference) {
        this.createCustomLengthArray(difference)
          .forEach(
            item => {
              this.mails.push(this.fb.control(null, Validators.required));
            }
          );
      }
    
      createCustomLengthArray(length) {
        return (new Array(Math.abs(length)))
          .fill(null)
          .map((item, index) => index + 1);
      }
    }
    

    And in the Template:

    <form [formGroup]="form">
        <mat-form-field>
            <mat-select 
              placeholder="Number of winners to reward" 
              [value]="selected" 
              (selectionChange)="selectNumber($event.value)">
                <mat-option 
                  *ngFor="let emailNumber of totalEmailsNumber" 
                  [value]="emailNumber">
                  {{ emailNumber }}
                </mat-option>
            </mat-select>
        </mat-form-field>
    
        <div formArrayName="mails">
            <mat-form-field 
              *ngFor="let email of form.controls['mails'].controls; let i = index">
                <textarea 
                  matInput 
                  [formControlName]="i">
          </textarea>
        </mat-form-field>
      </div>
    </form>
    

    Here's a Working Sample StackBlitz for your ref.