Search code examples
angulartypescript

How to correctly dynamically render components in angular in a ngFor loop


I have worked a lot with angular loops but somehow in this case I cant properly render even simple UI elements in a loop. A select element determines what is rendered in the loop below. The loop seems to evaluate the function again and again without user input in the following example:

  achievementTypes = [
    { value: 'photos_containing_all_of', label: 'Photos Containing All Of' },
    { value: 'photos_containing_only_category', label: 'Photos Containing Only Category' },
    { value: 'photos_of_distinct_groceries', label: 'Global: Different Categories across all images' },
    { value: 'photos_contains_at_least', label: 'Photos Containing At least x groceries' },
  ];
    <select [(ngModel)]="selectedType" name="selectedType" [ngModelOptions]="{ standalone: true }" class="custom-select">
        <option *ngFor="let type of achievementTypes" [value]="type.value">{{ type.label }}</option>
    </select>

    <div *ngIf="selectedType">
      <div *ngFor="let param of getRequiredParams(selectedType)">
          <span>{{ param.label }}</span>
          <!-- Uncommenting this will freeze the UI upon select -->
          <!-- <input [(ngModel)]="achievementParams[param.key]" /> -->
      </div>
    </div>

This is my function which called in the loop. I intend to use its values for the ui components to be rendered dynamically:

  getRequiredParams(type: string): any[] {
    let result = [{key: 'missing',  label: 'missing label'}];
    switch (type) {
      case 'photos_containing_all_of':
        result = [
          { key: 'setOfGroceries', label: 'Set of Groceries' },
          { key: 'requiredPhotoCount', label: 'Required Photo Count' }
        ];
        break;
      case 'photos_containing_only_category':
        result = [
          { key: 'category', label: 'Category' },
          { key: 'requiredPhotoCount', label: 'Required Photo Count' }
        ];
        break;
      case 'photos_of_distinct_groceries':
        result =  [
          { key: 'requiredDistinctGroceries', label: 'Required Distinct Groceries' }
        ];
        break;
      case 'photos_contains_at_least':
        result = [
          { key: 'groceryCount', label: 'Grocery Count' },
          { key: 'requiredPhotoCount', label: 'Required Photo Count' }
        ];
        break;
      // Add more cases for other types
      default:
        result = [{key: 'missing',  label: 'mssing label'}];
    }
    console.log(result);
    return result;
  }

Once I select a value and selectedType is set the getRequiredParams function gets called constantly which I can see in the console. As a consequence the UI freezes once I comment in the input element. I don't understand what the problem is. No error is thrown so far.

Maybe it is not important but component is a dialog component. I am using Angular 12.

How to do this the right way?


Solution

  • The problem is with you calling the getRequiredParams() inside the template. By default the component will rerender like crazy for every change made in the change detection tree.

    Maybe try something like...

    <select ... (ngModelChange)="updateRequiredParams($event)">...</select>
    
    @Component({...}) {
      ...
      updateRequiredParams(achievmentType: string) {
        this.requiredParams = this.getRequiredParams(achievmentType)
      }
    }
    

    ... and *ngFor the requiredParams