Search code examples
angularangular-reactive-formsangular-formbuilder

Angular Reactive Forms, checkboxes are not generated on incoming data


There is a form with filters. I create the form through FormBuilder. There are no problems with static data, but I cannot cope with incoming data. I need to render a group of checkboxes in airlines, similar to transferFilter. With the help of pathValue I change the value of the field to the processed data, 10 elements come out. In

 {{filtersForm.value | json}} 
, the data is displayed and changed, well, checkboxes do not appear. I don’t want to split the form into separate entities, but if I don’t find the answer, I’ll have to redo it.

Method for forming a group of checkboxes:

private mapToCheckboxArrayGroup (data: string [], display): FormArray {
    return this.fb.array (
      data.map ((val, i) => {
        return this.fb.group ({
          value: val,
          display: display [i],
          selected: false,
        });
      })
    );
  }

I create a group:

filtersForm: FormGroup = this.fb.group ({
    sort: [this.priceSortItems [0] .attribute],
    transferFilter: this.mapToCheckboxArrayGroup (
      this.availableTransferValue,
      this.availableTransferDisplay
    ),
    minCost: [null],
    maxCost: [null],
    airlines: [null],
  });

I receive data, process it and form a group using the method:

private refresh $ = new Subject ();

carriers $ = this.refresh $ .pipe (
    startWith (true),
    switchMap (() => this.dataService.getCarriers ()),
    map ((e) => {
      const keys = [... Object.keys (e)];
      const values ​​= [... Object.values ​​(e)];
      const airItems = this.mapToCheckboxArrayGroup (keys, values);
      return airItems;
    }),
    shareReplay (1)
  );

I react to changes in the form:

filterData $ = this.refresh $ .pipe (
    startWith (this.filtersForm.value),
    switchMap (() => this.filtersForm.valueChanges),
    map ((e) => {
      console.log (e);
      console.log (this.filtersForm.controls);
    }),
    shareReplay (1)
  );

I subscribe to the data update and through PathValue change the value of the airlines group to formarray from the method:

ngOnInit (): void {

    this.carriers $ .pipe (takeUntil (this.destroy $)). subscribe ((e) => {
      this.filtersForm.patchValue ({
        airlines: e.value,
      });
    });
}

I send an array with data to template via getter:

get carrierFilterArray () {
    const carriers = this.filtersForm.get ('airlines') as FormArray;
    console.log (carriers.value);
    return carriers;
  }

HTML Form:

<form * ngIf = "filtersForm.value" [formGroup] = "filtersForm" class = "page__menu menu-page">
    <div class = "menu-page__sort" * ngIf = 'priceSortItems'>
      <h3 class = "menu-title"> Sort </h3>
      <div class = "menu-page__sort items" * ngFor = "let elem of priceSortItems; let i = index">
        <input formControlName = "sort" [value] = "elem.attribute" type = "radio" [id] = "elem.value" /> <label
          [for] = "elem.value"> {{elem.display}} </label> <br />
      </div>

      <div class = "menu-page__filter" formArrayName = "transferFilter">
        <h3 class = "menu-title"> Filter </h3>
        <div class = "menu-page__filter items" * ngFor = "let elem of transferFilterArray.controls; let i = index"
          [formGroup] = "elem">
          <input type = "checkbox" [formControl] = "elem.get ('selected')" [value] = "elem.get ('value'). value"
            [id] = "elem.get ('value'). value" />
          <label [for] = "elem.get ('value'). value"> {{elem.get ('display'). value}} </label> <br />
        </div>
      </div>

      <div class = "menu-page__price">
        <h3 class = "menu-title"> Price </h3>
        <p>
          From
          <input formControlName = "minCost" type = "text" />
        </p>
        <p> Before <input formControlName = "maxCost" type = "text" /> </p>
      </div>

      <div class = "menu-page__airlines" formArrayName = "airlines">
        <h3 class = "menu-title"> Airlines </h3>
        <div class = "menu-page__filter items" * ngFor = "let elem of carrierFilterArray.controls; let i = index"
          [formGroup] = "elem">
          <input type = "checkbox" [formControl] = "elem.get ('selected')" [value] = "elem.get ('value'). value"
            [id] = "elem.get ('value'). value" />
          <label [for] = "elem.get ('value'). value"> {{elem.get ('display'). value}} </label> <br />
        </div>
      </div>
      <pre> {{filtersForm.value | json}} </pre>
    </div>
  </form>

As a result, I get all normal filters, except for the last one. Below you can see the json form.value, which contains all the data

I understand what needs to be done asynchronously, but I don't understand how ... The project is educational, at first I made it in pure JS, now I want to finish it in Angular. I would be very grateful for a tip.


Solution

  • You need to wait until the incoming data arrives and then you create the form with the required data's

    and put a

     <div *ngIf="anyKey">
           **//Here you need to render your form** 
        </div> 
    

    After receiving the data you need to generate your form.

        anyKey = false;
        filtersForm: FormGroup; 
    
       constructor(){
        this.refresh $ .pipe (
            startWith (true),
            switchMap (() => this.dataService.getCarriers ()),
            map ((e) => {
              const keys = [... Object.keys (e)];
              const values ​​= [... Object.values ​​(e)];
              const airItems = this.mapToCheckboxArrayGroup (keys, values);
    
              //Create your form here and make the key as true
              this.filtersForm = this.fb.group ({
                  sort: [this.priceSortItems [0] .attribute],
                  transferFilter: this.mapToCheckboxArrayGroup (
                     this.availableTransferValue,
                     this.availableTransferDisplay
                  ),
                  minCost: [null],
                  maxCost: [null],
                  airlines: airItems 
              });
              this.anyKey = true;
    
            }),
            shareReplay (1)
          );
    }
    

    i think this will help you for rendering all the controls.