2

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.

4
  • 1
    This is due to change detection, you are calling a method on your template expression. isTrue(dataEntry) this is the issue. Commented Mar 11, 2021 at 19:55
  • @penleychan I changed it to use dataEntry.boolean instead of isTrue(dataEntry). Still the same. Also - change detection is called twice on second click on that component. Commented Mar 11, 2021 at 20:02
  • Please add the relevant codes to your updated question. What is dataEntry what's in your onChange? Commented Mar 11, 2021 at 20:05
  • In developement, change detection is run twice. Commented Mar 11, 2021 at 20:08

1 Answer 1

5

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>

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

Comments

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.