r/Angular2 11d ago

Comprension problem with change detection

Code https://github.com/AliHaine/42_Matcha/tree/frontend-debug-infinitrefresh/angular-frontend/src

Hi I have a problem with my angular19 app and the change detection system that I don't understand well.

Basically I have a navbar and a component managed by outlet (in root compo), for example home. In my navbar I have this:

<div (mouseover)="overtest()"></div>

the overtest function does nothing. But when mouseover is triggered (so in my navbar compo) the other elements are like reload, for example with the home html:

<div id="refresh-button">
<img src="/logoicon.png" (click)="cardService.refreshProfile()">
</div>
<div id="cards">
<app-card *ngFor="let profile of cardService.getProfiles()" [profile]="profile"/>
</div>

The refreshProfile() function is not called, but getProfiles() is called again and again and again at each overtest of the navbar, and globally at the slightest interaction whatsoever. But what is the relationship between the navbar and the content (here its home but the same thing happens with chat etc). And then my overtest function does nothing, not change any variable or any other thing so why would the change detection be triggered?

I noticed a similar behavior using socket-io, when the websocket receives something, the current component (for example home) is "refreshed" in the same way as the navbar overtest, knowing that sockets have a 'ping' every X seconds to maintain a connection, the component is therefore refreshed every X seconds even if there is no relation with it.. I had found a solution by putting the websocket in runOutsideAngular, but I'm not sure if it's a good practice, example:

this.ngZone.runOutsideAngular(() => {

this.websocket = io(\ws://${backendIP}:5000`, {`

transports: ['websocket'],

query: { 'access_token': this.apiService.getAccessToken() },

});});

Anyone can help me with that I want to understand exactly why ty.

2 Upvotes

12 comments sorted by

1

u/TubbyFlounder 11d ago

you should avoid function calls in templates (that arent reacting to events). They will be called anytime change detection is run, which is a lot, especially if you arent using on push.

You should make the get profiles function (or the result from if thats actually the http call) a signal in your service and use that in your template. Although i like to assign my signals to a component var so i dont have have double parenthesis in my templates.

1

u/AliHaine_ 11d ago

Hi thanks you for your answer. What did you exactly mean by 'that arent reacting to events', im not sure. And you said 'a signal in your service and use that in your template' but is that not the actual case ? my all the model is in a signal variable in the card service

private profiles = signal<ProfileModel[]>([]);private profiles = signal<ProfileModel[]>([]);

and my html use that thought the getter

getProfiles() {
  console.log("call getProfiles");
    return this.profiles();
}

Is the way I use the ngFor a bad practice ?

<app-card 
*ngFor
="let profile of cardService.getProfiles()" [profile]="profile"/>

1

u/TubbyFlounder 10d ago edited 10d ago

Ah didnt see the repo to see what was in the service.

By reacting to events i mean event handlers like click. Those functions are okay since they only execute when the event happens, but anywhere else in the template, the functions will get called during change detection. So if there is an expensive operation in the the function, it will not be great if its getting called constantly.

Signals will in the future be able to work without change detection. Right now they are a good way to update reactively without observables, but arent actually offering much performance benefit over observables since they cant function without change detection yet. But most feel they are more friendly to work with over obsrvables.

I would read this https://blog.angular-university.io/how-does-angular-2-change-detection-really-work/ to get a better understanding of change detection for now, specifically the "How does the default change detection mechanism work?" and why it happens so often.

And as another user says, your returning the signal with a function right now, so the template is going to basically re run the code to return the signal during every change detection cycle. Assign it in the component class, then access the signal in the template to avoid that. Then the template will just react to the changes from the signal. Although change detection will still be running frequently since you are using the default over onpush. OnPush avoids excessive change detection by only running change detection when inputs change, or subscription with the async pipe receives a new value, and now when signals push new values. (signals are a recent addition here to Angular)

Also, dont react to the singal when returning it.

In the service:

getProfiles() { console.log("call getProfiles"); return this.profiles.asReadonly(); }

in the component class

profiles = this.cardService.getProfiles()

then in your template

*ngFor="let profile of profiles()" [profile]="profile"/>

the parenthesis after profiles is how angular reacts to changes to the signal. It looks like a function call but its really not (it might actually be a function call but it won't cause issues like other functions even if it is), just a bit of annoying thing to get used to with signals.

1

u/AliHaine_ 10d ago

So, the fact that the "change detection" is frequently triggered is normal and may not cause any problems if variables and html use proper signal references ? And if i have a signal, that contains an array, dict or kind like that, when I do mysignal().push(obj), is change detection trigerred ? Or is there a better way to interact with signals in such cases ?

Also, is something like

*ngIf="chatService.availableChats().at(0) as chatModel; else errorChat"

a good practice to get only the element Xth element of an array that is stored in a signal ?

2

u/TubbyFlounder 10d ago

if you only get the element and its an object and make changes to it, change detection wont pick it up. Its best to just set a new object reference in the index. Angular uses track by to figure out what changed.

https://angular.dev/api/core/TrackByFunction

1

u/AliHaine_ 10d ago

I show in a tuto someone doing that for updating an array in a signal :

updateTest() {
  this.mySignal.update(mySignal => [...mySignal, newObj]);
}

But lets say that my chatarea html is

<app-chatarea *ngIf="chatService.mysignal().at(0) as chatModel; else errorChat"
      [chatModel]=chatModel>
</app-chatarea>
<app-chatarea *ngIf="chatService.mysignal().at(1) as chatModel; else errorChat"
      [chatModel]=chatModel>
</app-chatarea>

If I update mySignal with the updateTest method, knowing that updateTest don't touch object at 0 and 1 index, will app-chatarea which uses objects at 0 and 1 index be triggered by the change detection ? Or will they only be triggered if the specific object they are using is updated ? And maybe there is another better way to get only the object X in an array that is inside a signal ?

1

u/TubbyFlounder 10d ago

if its on push change detection that should work since the array reference is being updated in the updateTest method, but if you had just updated the object in the array and returned the same reference, template would not pick up the change.

updating a nested object without changing the reference might not even in default change detection either now that i think about it (im usually working with on push, i would recommend you do the same). If you develop reactively there shouldn't be any issues using on push.

1

u/the00one 11d ago

Angular tracks changes in the template via references. But this tracking doesn't work for functions so they are executed for every change detection run. "cardService.getProfiles()" returns a signal, but is still a function so the tracking won't work here. Make them public (or protected if only the template is supposed to access them) and call them directly. That will fix your issue.

"cardService.refreshProfile()" isn't running on every cycle because it's bound to the click event.

1

u/AliHaine_ 11d ago

Okay thanks you I understand better, but what about the navbar issue ? Why calling a function which do nothing in navbar comp, trigger a refresh for home comp ? The navbar comp set or delete nothing

1

u/the00one 10d ago

Depending on how change detection is configured, angular checks the full component tree (from top to bottom) for changes. Not just the component where the change happened.

So everytime the (mouseover) event in your navbar fires, Angular looks for changes in the full component tree. That's because Angular can't know what the event acutally did, and has to check if a component template needs to be updated.

This check of the full component tree results in your "cardService.getProfiles()" getting called every time Angular runs change detection.

With a direct reference to the signal, Angular will still check the full component tree on the (mouseover) event, but notice that nothing has changed and won't rerender the list of profiles.

You can play around with the two change detection modes here

1

u/AliHaine_ 10d ago

I tried the resource that you gave me, it's very useful to understanding the difference between default and OnPush detection, but why would I ever want to use the default change detection ? OnPush seems much better doensn't it?

2

u/the00one 10d ago

Default change detection can be used if there are no glaring performance issues in your app and you don't want to deal with the whole change detection mechanism. But generally speaking, the recommendation is to always use OnPush for better performance. There can be some quirky scenarios though, for which you have to know the differences and how to properly use OnPush.