angularangular-changedetection

Will angular recall a function performed on an *ngIf variable every change detection cycle?


I'm a little bit confused regarding the best practice when using a variable with ngIf. Suppose we have the following component that shows error messages. It is a component that is used heavily everywhere the project so we try to make it as light as possible. Pay attention specifically to stringUtils.isNotEmpty(message):

<div *ngIf="stringUtils.isNotEmpty(message)">
  <error-icon/>
  <div #errorMsg>{{message}}</div>
</div> 
import {Injectable} from '@angular/core';

@Injectable({
  providedIn: 'root'
})

export class StringUtilsService {
  
  isEmpty(str: string | null | undefined) {
    return !str || String(str)?.trim().length == 0;
  }
// other services
}

We think that this way to do it despite being clean and more concise, it will need to recall the function at each change detection cycle. While this shouldn't be a big deal for a single use component, as a reusable component we could improve it by inlining the condition:

<div *ngIf="message && message.trim().length > 0">
  <error-icon/>
  <div #errorMsg>{{message}}</div>
</div>

My question is:

get isEmpty(){
return !this.message || String(this.message)?.trim().length == 0;
}

Solution

  • To answer your questions:

    Will it recall the trim() function every cdc? since the message itself is a variable that hasn't changed.

    Yes. If the change detection isn't handled completely manually, then the function would be triggered for each cycle.

    for example is get in the component code treated same way?

    Yes.

    Is there a way in the middle where I can call the logic in one place to avoid duplication, while not checking on every cdc?

    Yes, quickest way would be to use a pure pipe. Try the following

    is-not-empty.pipe.ts

    import { Pipe, PipeTransform } from '@angular/core';
    
    @Pipe({
      name: 'isNotEmpty',
      pure: true,
    })
    export class IsNotEmptyPipe implements PipeTransform {
      transform(str: string | null | undefined): boolean {
        return !str || String(str)?.trim().length == 0;
      }
    }
    

    It could then be used in the template as

    <div *ngIf="message | isNotEmpty">
      <error-icon/>
      <div #errorMsg>{{message}}</div>
    </div>