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?

1

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Huh, this is interesting, I didn't know that you could define a setter function for the Input values.
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
Yep, that's exactly what I'm doing. Set the class name in the setter, and just have a single <i [ngClass]="className"></i>.
great, glad i could help. definitely easier to extend that way in the future to more icon classes
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.

2 Comments

setter input is usually preferred to ngOnChanges lifecycle hook
@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.
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`;
  }
}

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.