0

I'm making an Angular component that will be used to display a file icon depending on an input string. The input string will be checked against an array of some possible values for each file type.

For example,

  • show word icon if we see doc, docx, word.
  • Show excel icon if we see xls, xlsx, excel.

And so on, for each of the file types our app expects.

I was planning on doing a simple list of <i> elements with appropriate classes depending on which array the input string is in.

export class FileIconComponent {
  @Input() fileType: string;

  private readonly FILE_WRD: string[] = ["word", "doc", "docx"];
  private readonly FILE_PDF: string[] = ["pdf", "application/pdf"];
  private readonly FILE_EXL: string[] = ["excel", "xls", "xlsx"];

  constructor() {}
}
<i *ngIf="FILE_EXL.includes(fileType?.toLowerCase())" class="icon document-excel-o"></i>
<i *ngIf="FILE_WRD.includes(fileType?.toLowerCase())" class="icon document-word-o"></i>
<i *ngIf="FILE_PPT.includes(fileType?.toLowerCase())" class="icon document-powerpoint-o"></i>

However, since change detection runs many times per second, and I'll be displaying probably a hundred lines of this per page, I'm concerned about performance when I'm putting a function call in my template. Would making one <i> and use ngClass to determine the class be more performant?

Is there a more efficient way of doing this? Or am I over-concerned about performance in this case?

Rich
  • 1,567
  • 14
  • 24

3 Answers3

2

Endeed, function in the HTML is not recommended with the angular lifecycle.

In your case, you can't use a getter function because the result will depend of the value of the index.

For performance, you can:

  • Use the OnPush strategy for your component.
  • Use the trackBypipe to not reload everytime all your list.
  • Add a field in your object in the ngOnChanges where you specify for each item, the wanted class to avoid the use of function.
  • As @bryan60 suggests, you can use as an alternative of the ngOnChanges, a setter method for your input.
Wandrille
  • 6,267
  • 3
  • 20
  • 43
  • setter input is usually preferred to ngOnChanges lifecycle hook – bryan60 Oct 31 '19 at 21:16
  • @bryan60, you are right, with only one @Input(), a setter is interesting. But i'm not a big fan of “private” ghost property used in setter. – Wandrille Oct 31 '19 at 21:23
2

use a setter input, so you run the functions when needed....

export class FileIconComponent {
  private _fileType: string;
  @Input() set fileType(fileType: string) {
    this._fileType = fileType;
    this.isWord = this.FILE_WRD.includes(fileType.toLowerCase());
    this.isExcel = this.FILE_EXL.includes(fileType.toLowerCase());
    this.isPdf = this.FILE_PDF.includes(fileType.toLowerCase());
  };
  get fileType() { return this._fileType; }

  isWord = false;
  isExcel = false;
  isPdf = false;

  private readonly FILE_WRD: string[] = ["word", "doc", "docx"];
  private readonly FILE_PDF: string[] = ["pdf", "application/pdf"];
  private readonly FILE_EXL: string[] = ["excel", "xls", "xlsx"];

  constructor() {}
}

then simple and clean usage in template:

<i *ngIf="isExcel" class="icon document-excel-o"></i>
<i *ngIf="isWord" class="icon document-word-o"></i>
<i *ngIf="isPdf" class="icon document-powerpoint-o"></i>

setters only run when the input changes, so only as needed.

bryan60
  • 28,215
  • 4
  • 48
  • 65
  • Huh, this is interesting, I didn't know that you could define a setter function for the Input values. – Rich Nov 01 '19 at 13:57
  • yup, they're useful. you could even clean it up a bit by just figuring out the icon class in the setter and doing [ngClass]="iconClass" and just have the single icon directive – bryan60 Nov 01 '19 at 14:49
  • Yep, that's exactly what I'm doing. Set the class name in the setter, and just have a single ``. – Rich Nov 01 '19 at 14:51
  • great, glad i could help. definitely easier to extend that way in the future to more icon classes – bryan60 Nov 01 '19 at 14:53
1

You can add an Enum, and use it in the ngIf

import { Component, Input, OnInit } from "@angular/core";

enum FileTypeEnum {
  FILE_WRD,
  FILE_PDF,
  FILE_EXL
}

@Component({
  selector: "hello",
  template: `
    <i *ngIf="type === fileTypeEnum.FILE_EXL" class="icon document-excel-o"></i>
    <i *ngIf="type === fileTypeEnum.FILE_WRD" class="icon document-word-o"></i>
    <i *ngIf="type === fileTypeEnum.FILE_PDF" class="icon document-pdf-o"></i>
  `,
  styles: [
    `
      h1 {
        font-family: Lato;
      }
    `
  ]
})
export class HelloComponent implements OnInit {
  @Input() name: string;
  type: FileTypeEnum;
  fileTypeEnum = FileTypeEnum;
  private readonly fileTypes = {
    FILE_WRD: ["word", "doc", "docx"],
    FILE_PDF: ["pdf", "application/pdf"],
    FILE_EXL: ["excel", "xls", "xlsx"]
  };

  ngOnInit() {
    const fileType = Object.keys(this.fileTypes).find(key => this.fileTypes[key].includes(this.name));
    this.type = FileTypeEnum[fileType];
  }
}

But if this logic is only to know which icon to show, then you can use ngClass instead with only one i tag:

import { Component, Input, OnInit } from "@angular/core";

@Component({
  selector: "hello",
  template: `
    <i [ngClass]="className"></i>
  `,
  styles: [
    `
      h1 {
        font-family: Lato;
      }
    `
  ]
})
export class HelloComponent implements OnInit {
  @Input() name: string;
  className;
  private readonly fileTypes = {
    word: ["word", "doc", "docx"],
    pdf: ["pdf", "application/pdf"],
    excel: ["excel", "xls", "xlsx"]
  };

  ngOnInit() {
    const fileType = Object.keys(this.fileTypes).find(key =>
      this.fileTypes[key].includes(this.name)
    );
    this.className = `icon document-${fileType}-o`;
  }
}
yazantahhan
  • 2,950
  • 1
  • 13
  • 16
  • Putting the logic to determine which icon to show in `ngOnInit` means that logic will only be called once: when the component initializes. If the input changes, the icon wont update. I thought about the same thing at first, but ruled out this method as it doesn't handle change detection at all. – Rich Nov 01 '19 at 13:50