Kalik
Kalik

Reputation: 184

Angular *ngIf called multiple times, using async pipe and subscribe

I'm facing an issue with my current angular setup and I can't figure out what exactly is wrong. I will first describe some context before I go to the problem.

I have a class - data-service.ts, which has a method "getData" inside. This is the body:

getData(): Observable<Data[]> {
let params = new HttpParams();
params = params.append('user_id', this.userId);
return this.http.get<Data[]>('api/data', {
  observe: 'body',
  responseType: 'json',
  params
}).pipe(
    map((response: any[]) => {
      return response.map((s) => ({
        id: s.id,
        userId: s.user_id,
        type: s.type_id,
        ...
      }));
    })
);}

My requirement is to fetch this data and refresh the view every time user enters a targeted tab in ngModal. Modal component "modal-component.ts" fetches this data when you click that targeted tab. Clicking on the tab fires an event, by this piece of code:

getData(evt) {
  this.data$ = this.dataSevice.getData();
}

I'm passing this "data$" observable to child component using async pipe:

<modal-component>
...
    <app-data-manager [data]="data$ | async"></app-data-manager>
...
</modal-component>

Inside of "data-manager" I'm using *ngFor on [data], and for every data I'm rendering some html. In that HTML I'm using *ngIf to determine which template, first or second, should be rendered.

<div *ngIf="isTrue(dataEntry); then first else second"></div>

Problem is: isTrue method is called for every dataEntry, which is fine, but it's getting called a multiple times for entire data set. Like hundreads of times. I tried to using promises, using Take(1) pipe, assigning boolean (the one that ngIf uses to choose template) during mapping, assigning data inside of a subscribe and passing normal collection instead of using async pipe. I don't know that is causing this - I appreciate every help on that one. Thank you!

Additional info When I use dataEntry.isTrue instead of isTrue(dataEntry) in *ngIf - I'm still getting same results. I also added "OnChanges" in child component with console log inside - on first click it logs to console once, on second click it logs to console twice. Two same messages appear.

Upvotes: 2

Views: 2821

Answers (1)

g0rb
g0rb

Reputation: 2379

The problem is you are using a function call in an angular expression. Don't do this.

[T]he isTrue() function is executed every time Angular change detection runs. And that can be many times!

Because Angular cannot predict whether the return value of isTrue() has changed, it needs to execute the function every time change detection runs.

This means that any change detection throughout the app (including outside of app-data-manager component) will cause isTrue() to execute.

This article talks about it more: https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496

One solution is to use properties instead of a function. If you want to wrap up complex logic, opt for making a custom pipe like so.

*ngIf="(dataEntry | isTruePipe); then first else second"></div>

Upvotes: 5

Related Questions