Angular - *ngIf vs simple function calls in template

I also tried to avoid functions calls in templates as much as possible, but your question inspired me to do a quick research:

I added another case with caching userCheck() results

*ngIf="isUserChecked"

...
// .ts
isUserChecked = this.userCheck()

Prepared a demo here: https://stackblitz.com/edit/angular-9qgsm9

Surprisingly it looks like there is no difference between

*ngIf="user && user.name && isAuthorized"

And

*ngIf="userCheck()"

...
// .ts
userCheck(): boolean {
  return this.user && this.user.name && this.isAuthorized;
}

And

*ngIf="isUserChecked"

...
// .ts
isUserChecked = this.userCheck()

This looks like it's valid for a simple property checking, but there definitely will be a difference if it comes to any async actions, getters that are waiting for some api for example.


This is a pretty opinionated answer.

The usage of functions like this, is perfectly acceptable. It will make the templates much clearer, and it does not cause any significant overhead. Like JB said before, it will set a much better base for unit testing as well.

I also think that whatever expression you have in your template, will be evaluated as a function by the change detection mechanism, so it doesn't matter if you have it in your template or in your component logic.

Just keep the logic inside the function to a minimum. If you are however wary about any performance impact such a function might have, I strongly advise you to put your ChangeDetectionStrategy to OnPush, which is considered best practice anyways. With this, the function won't be called every cycle, only when an Input changes, some event happens inside the template, etc.

(using etc, because I don't know the other reason anymore).


Personally, again, I think it's even nicer to use the Observables pattern, you can then use the async pipe, and only when a new value gets emitted, the template gets re-evaluated:

userIsAuthorized$ = combineLatest([
  this.user$,
  this.isAuthorized$
]).pipe(
  map(([ user, authorized ]) => !!user && !!user.name && authorized),
  shareReplay({ refCount: true, bufferSize: 1 })
);

You can then just use in the template like this:

<ng-template *ngIf="userIsAuthorized$ | async">
 ...
</ng-template>

Yet another option would be to use ngOnChanges, if all the dependent variables to the component are Inputs, and you have a lot of logic going on to calculate a certain template variable (which is not the case you showed):

export class UserComponent implements ngOnChanges {
  userIsAuthorized: boolean = false;

  @Input()
  user?: any;

  @Input()
  isAuthorized?: boolean;

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.user || changes.isAuthorized) {
      this.userIsAuthorized = this.userCheck();
    }
  }

  userCheck(): boolean {
    return this.user && this.user.name && this.isAuthorized || false;
  }
}

Which you can use in your template like this:

<ng-template *ngIf="userIsAuthorized">
 ...
</ng-template>