How to implement complex [ngClass] calculation in .ts instead of .html?

76 Views Asked by At

Our team is developing a pretty complex form engine that has many important nodes such as form, container, field and etc. Needless to say, class calculation for these nodes is pretty complex - there can be 10+ conditional classes with very complex conditions. As Angular offers us many ways to implement component logic in .ts instead of .html we used function way to do it, like this:

[ngClass]="calculateSomeDamnComplexConditionsAndReturnAHugeNGClassObject()"

But as I google the topic I see many "function calls in template is bad practice" comments like here or here. Ok, function calls in template is bad practice and can cause performance issues, but it would be pretty insane in 2023 to implement all complex classname calculations right in template. Template is not a place for complex calculations like this:

  <p [ngClass]="{
    'text-success':celeb.country === 'USA',
    'text-secondary':celeb.country === 'Canada',
    'text-danger':celeb.country === 'Puorto Rico',
    'text-info':celeb.country === 'India'
  }">{{ celeb.artist }} ({{ celeb.country }})

This example has only 4 conditional classes, but what if there are many of them and also there are many nodes that need complex conditional classes calculations

So the question is: angular guys, how do you implement complex classname calculations in component class without performance issues in 2023?

2

There are 2 best solutions below

1
Konstantin Grig On

I think then calculate css class string not so bad for performance. But you can use variable when it just component calculateSomeDamnComplexConditionsAndReturnAHugeNGClassObject and assign method that return the value in your component ngOnInit(). When you use loop ngFor you could make additional field like celebs.forEach(item => item.style = this.getStyle(item.country))} and use in template as [ngClass]=celeb.style.

3
Askirkela On

Alternatively, you could move the calculation to a custom pure pipe that returns a string containing all the necessary classes.

That way, you can even split your calculations in the pipe's methods.

@Pipe({name: 'complexClassPipe'})
export class ComplexClassPipe implements PipeTransform {
  transform(value: ArrayItem) {
    return this.getClassList(value);
  }

  private getClassList(item: ArrayItem) {
    return [this.getClassForCountry(item.country)/*, call other methods*/].join(' ');
  }

  private getClassForCountry(country: string) {
    switch (country) {
      case 'USA':
        return 'text-success';
      case 'Canada':
        return 'text-secondary';
      case 'Puorto Rico':
        return 'text-danger';
      case 'India':
        return 'text-info';
    }
  }
}

Then use it like this:

<p [ngClass]="celeb | complexClassPipe">{{ celeb.artist }} ({{ celeb.country }})