How to set temporary variable inside ngFor with Angular 11?

Viewed 1469

I have the following html that is calling these: getMetaForXXX methods multiple times inside an ngFor block. I feel like this isn't good practice and was hoping there was a way I could simplify this logic.

  <div *ngFor="let i of state.data" class="carousel-cell" tabindex="-1">
    <div class="carousel-cell-primary">
      <span class="large">{{getMetaForVendor(i.vendor).display}}</span>
      <span class="large">{{i.name}}</span>
      <span>{{i.issued | monthYear}} &mdash; {{i.expiry | monthYear}}</span>
      <span>Credential ID: {{i.credential || "Not Applicable"}}</span>
      <span>
        <mat-slider
          aria-label="units"
          [disabled]="true"
          [max]="4"
          [min]="0"
          [value]="getMetaForLevel(i.level).rank"
        >
        </mat-slider>
        {{getMetaForLevel(i.level).display}}
      </span>
    </div>
  </div>

component.html

  state: CertificationState;

  getMetaForLevel(value: string): MetaData {
    return this.state.meta.levels.find(meta => meta.name === value);
  }

  getMetaForVendor(value: string): MetaData {
    return this.state.meta.vendors.find(meta => meta.name === value);
  }

component.ts

export interface CertificationState {
  data: Certification[];
  meta: CertificationMeta;
}

export interface Certification {
  id: number;
  credential: string;
  description: string;
  expiry: MonthYear;
  issued: MonthYear;
  level: string,
  name: string;
  vendor: string;
}

export interface CertificationMeta {
  levels: MetaData[];
  vendors: MetaData[];
}

export interface MetaData {
  name: string;
  display: string;
  description: string;
  rank: number;
}

model.ts

{
  "data": [
    {
      "id": "1",
      "name": "Cloud Practitioner",
      "credential": "1234",
      "description": "Lorem ipsum dolor sit amet.",
      "expiry": {
        "month": 7,
        "year": 2023
      },
      "issued": {
        "month": 12,
        "year": 2018
      },
      "level": "FOUNDATIONAL", <-- correlates to meta.levels.name
      "vendor": "AWS"          <-- correlates to meta.vendors.name
    }
    ...
  ],
  "meta": {
    "levels": [
      {
        "name": "FOUNDATIONAL",
        "description": "lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris venenatis purus",
        "display": "Associate",
        "rank": 2
      }
      ...
    ],
    "vendors": [
      {
        "name": "AWS",
        "display": "Amazon Web Services (AWS)"
      }
      ...
    ]
  }
}

example model data

2 Answers

One way to create a template variable is with *ngIf. In the code below, the directive is applied to the div element inside the *ngFor loop :

  • To ensure that the *ngIf condition is true, an object is created and evaluated
  • The value returned by the expression is assigned to a property of the object
  • The object is assigned to a new template variable, metaForLevel
  • You can then use that variable multiple times in the loop iteration
<div *ngFor="let i of state.data" ...>
  <div *ngIf="{data: getMetaForLevel(i.level)} as metaForLevel" ...>
    ...
    <span>
      <mat-slider ... [value]="metaForLevel.data.rank"></mat-slider>
      {{ metaForLevel.data.display }}
    </span>
  </div>
</div>

In cases where an HTML container is not present or desirable inside the *ngFor loop, the *ngIf condition can be applied to an ng-container element.

you should create a pipe here :-

@Pipe({name: 'displayVal'})
export class DisplayValuePipe implements PipeTransform {
  transform(val, state): string {
     return state.meta.levels.find(meta => meta.name === value.vendor)?.display;
  }
}

and use it like :-

<span class="large">{{i | displayVal: state}}</span>
Related