Search code examples
angularangular-reactive-forms

How to create dynamic form with async pipe in angular


I am trying to create a dynamic angular form the data that I get from an ngRx service.

Here is the sample code. My html template looks like

    <div class="padding-top-50" *ngIf="filteredParam$ | async; else loader">
      <form class="row" [formGroup]="paramForm" *ngIf="paramForm">
        <div class="col-sm-4 padding-top-30" *ngFor="let reportParam of formData">
          <label>{{ reportParam.name | textTrimmer }}</label>
          <ng-container [ngTemplateOutlet]="reportParam.type === 'Date'? dateField: textField"
            [ngTemplateOutletContext]="{reportParam: reportParam}">
          </ng-container>
        </div>
      </form>
    </div>
<ng-template #textField let-reportParam="reportParam">
  <div class="input-group">
    <input #inputField type="text" [name]="reportParam.name" class="form-control"
      placeholder="{{ 'vrvReportTranslation.item' | cxTranslate }}" (change)="inputChanged(reportParam)" />
  </div>
</ng-template>
<ng-template #dateField let-reportParam="reportParam">
  <div class="input-group">
    <input #inputField type="text" [name]="reportParam.name" class="form-control ng-datepicker" placeholder="MM-DD-YYYY"
      ngbDatepicker #d1="ngbDatepicker" (dateSelect)="d1.toggle();" [positionTarget]="buttonFrom"
      [formControlName]="reportParam.name" (change)="inputChanged(reportParam)" [firstDayOfWeek]="7" />
    <button class="btn btn-outline-secondary bi-calendar3 margin-0" #buttonFrom (click)="d1.toggle();" type="button"
      [ngClass]="'active-btn'">
      <span class="calendar-icon"></span>
    </button>
  </div>
</ng-template>

And my component file looks like this:

  formData: ReportsParam[] = [];
  paramForm: FormGroup | undefined;
  filteredParam$ = this.reportService.getReportParams();

  ngOnInit(): void {
    this.filteredParam$.subscribe(data => {
      // update the array in case subscription triggers again
      this.formData.length = 0;
      let formObj: { [key: string]: FormControl } = {};
      const previousMonthDate = this.getLastMonthDate();
      console.log(this.paramForm);
      if (data)
        data.forEach(element => {
          formObj[element.name] = this.formBuilder.control('');
          this.formData.push(element);
        })
      this.paramForm = this.formBuilder.group(formObj);
      console.log(this.paramForm);
    });
  }

Below is the error I am getting

Error message

I understand the error is happening because paramForm is getting its value after the view has been created thus throwing this error, but I am not able to solve this. I don't know:

  1. How to resolve the error, checking if formObject has values doesn't solve the issue.
  2. Is it even the best/correct approach to create a dynamic form for an API.

Solution

  • Found the issue. If you are using ng-container for formGroup you need to tell the templates which formgroup you want to use. So the template should look like this :

        <div class="padding-top-50" *ngIf="filteredParam$ | async; else loader">
          <form class="row" [formGroup]="paramForm">
            <div class="col-sm-4 padding-top-30" *ngFor="let reportParam of formData">
              <label>{{ reportParam.name | textTrimmer }}</label>
              <ng-container [ngTemplateOutlet]="reportParam.type === 'Date'? dateField: textField"
                [ngTemplateOutletContext]="{reportParam: reportParam}">
              </ng-container>
            </div>
          </form>
       </div>
    <ng-template #textField let-reportParam="reportParam">
      <div class="input-group" [formGroup]="paramForm">
        <input #inputField type="text" [name]="reportParam.name" class="form-control"
          placeholder="{{ 'vrvReportTranslation.item' | cxTranslate }}" (change)="inputChanged(reportParam)" />
      </div>
    </ng-template>
    <ng-template #dateField let-reportParam="reportParam">
      <div class="input-group" [formGroup]="paramForm">
        <input #inputField type="text" [name]="reportParam.name" class="form-control ng-datepicker" placeholder="MM-DD-YYYY"
          ngbDatepicker #d1="ngbDatepicker" (dateSelect)="d1.toggle();" [positionTarget]="buttonFrom"
          [formControlName]="reportParam.name" (change)="inputChanged(reportParam)" [firstDayOfWeek]="7" />
        <button class="btn btn-outline-secondary bi-calendar3 margin-0" #buttonFrom (click)="d1.toggle();" type="button"
          [ngClass]="'active-btn'">
          <span class="calendar-icon"></span>
        </button>
      </div>
    </ng-template>
    

    Rest all the things will be same. Also one *ngIf was not required. Not marking this as answer because it doesn't answer the second part, i.e. Is it even the best/correct approach to create a dynamic form for an API.