Angular form: How to dynamically add/remove subform Component

Viewed 1138

I would like in a Main form to be able to add/remove dynamically a subForm. the subform is based on a model object, and embeds several things :

  • use data transmitted from parent form component (for select input choices)
  • has event trigger onChange function
  • May be used in edit mode, such may permit form pre-fill

I then create a dedicated component for this subform:

Model:

impoort { Obj } from ./Obj.model.ts
export class Foo
 {
  constructor(
  public name: string,
  public obj: Obj,
    public bar: string
  ) {}
}

subform.component.ts:


@Component({
  selector: 'ngx-subformcomp',
  templateUrl: './subform.component.html',
  styleUrls: ['./subform.component.scss']
})
export class SubformComponent implements OnInit, OnDestroy {

  @Input() objs: Obj[];
  @Input() foodata?: Foo;

  subForm: FormGroup;
  showBar: boolean = true;

  constructor(
    private formBuilder: FormBuilder,
    private loadingService: LoadingService,
  ) { }

  ngOnInit(): void {
    initForm();
    if (foodata) {
      //In case Edit mode
      this.subForm.patchValue({
        name: foodata.name,
        obj: foodata.obj,
        bar: foodata.bar,
      });
      if (foodata.obj.id == 1) {
        this.subForm.get('bar').setValidators([Validators.required]);
        this.subForm.get('bar').updateValueAndValidity();
        this.showbar = true;
      }
      else {
        this.subForm.patchValue({ bar: '' });
        this.subForm.get('bar').setValidators([]);
        this.subForm.get('bar').updateValueAndValidity();
      }
    }
  }

  initForm() {
    this.subForm = this.formBuilder.group({
      name: ['', [Validators.required],
        obj: new FormControl('', [Validators.required, SelectedValue]),
        bar: ['', [Validators.required],
          },
      {
        validator: [... , ...]
      });
  }

  onObjChange(event) {
    if (event == 1) {
      this.subForm.get('bar').setValidators([Validators.required]);
      this.subForm.get('bar').updateValueAndValidity();
      this.showbar = true;
    }
    else {
      this.subForm.patchValue({ bar: '' });
      this.subForm.get('bar').setValidators([]);
      this.subForm.get('bar').updateValueAndValidity();
    }

  }
}

and html:

<form [formGroup]="subForm">    
    <div class="form-group">
      <input id="name" class="form-control" formControlName="name" />   
    </div>
    <div class="form-group">
      <nb-select id="obj" formControlName="obj" (selectedChange)="onObjChange($event)">
        <nb-option *ngFor="let option of objs" [value]="option.id">{{ option.label }}</nb-option>
      </nb-select>
    </div>
    <div class="form-group">
      <input *ngIf="showBar" id="bar" class="form-control" formControlName="bar" />   
    </div>
 </form>

I try now to use it in my main form but I am lost...

I defined my main form component like that:

export class MainComponent implements OnInit, OnDestroy {
  objs:Obj[] = [{....}],
  mainForm: FormGroup;

 ngOnInit(): void {
  initForm();
 }   

 initForm() {
    this.mainForm = this.formBuilder.group({
      foobar: ['', [Validators.required],
  objs: this.formBuilder.array([]),
    }, 
    {
      validator: [... ,...]
    }); 

 }

getObjs(): FormArray {
  return this.sMainForm.get('objs') as FormArray;
}

onAddObj() {
}

and in my template:

<div class="form-group">
    <ng-container formArrayName="objs" *ngFor="let obj of objs; let i=index">
        <ngx-subformcomp id="'gsprice_'+i" [formControlName]="objs[i]"></ngx-subformcomp>
    </ng-container>
    <button (click)="onAddObj()" outline>Add}</button>
</div>

I do not know how to treat the onAddObj function neither the coming removeObj... Thank you for your help

1 Answers

I see a lot of confusion here (:

First, that is not how you can iterate FormArray. The correct way would be

<div *ngFor="let ctr of objs.controls;">

So that the ctr would contain the FormGroup. Next one, for ngx-subformcomp to communicate with form control provided with [formControlName]='obj' or [formControl]='obj', you need to implement control value accessor interface. It could be tricky at first. To make a smaller step forward you might want to pass the ctr right to the ngx-subformcomp.

And the biggest one At first glance, it always seems a good idea to separate the responsibilities of managing parts of the form between controls. Like one component manages the array, another component manages the items. In practice it becomes painful. Soon logic gets spread among several components. It gets hard to comprehend all the validations and dependencies between form controls. In my experience, for the complex forms it is always easy to manage one service containing all the validations, all the dependencies, all the structure, and helpers specific to that one form. Such a service is easy to cover with unit tests as well. And components are used only to show the data and bind user input to HTML controls, nothing more. With such an approach it is way easier to maintain the code

Related