Reputation: 6687
I am working on a big application and I am having some issues with change detection.
Parent Component ts:
Using changeDetection: ChangeDetectionStrategy.OnPush
I have a variable that is an observable
loaderOverlay$: Observable<boolean>;
this.loaderOverlay$ = this.store.pipe(
select(selectors.loaderOverlaySelector)
);
This variable gets updated from an rxjs action from a child component. Then goes through the rxjs process. (Action -> Reducer -> Selector)
Parent Component HTML
<div *ngif="(loaderOverlay$ | async)"></div>
Child #1 Component (where I'm dispatching my action):
myFunction() {
this.store.dispatch(new actions.LoaderOverlay(true));
}
My issue is that once I dispatch the action, the *ngif
is very shaky. It doesn't seem to work the way I want it to (dispatch the action, change the value to true so the div appears). It's very strange because if I console.log(action.payload)
in the reducer, the value is actually being updated, but the *ngif
isn't working. And what's even stranger is when I hover over some other component, it seems to kick in.
I think I've narrowed it down to change detection because in the parent component if I do:
ngAfterViewChecked(){
this.changeDetector.detectChanges();
}
It seems to work for me. My issue with this is that ngAfterViewChecked
seems to get triggered a massive amount of times and I'm afraid of performance issues.
What might be going on here, and what I can do to fix this strange issue?
Upvotes: 10
Views: 1547
Reputation: 8295
Does your loaderOverlay$
emitted value evaluate to true
? I created a similar code (as you described) on stackblitz and I can't seem to be able to reproduce the bug you encounter. It's either a bug that was fixed in angular/ngrx, or the value you are emitting does not evaluate to true (the select is not good and returns undefined or something). Try tapping the observable and logging the results.
https://stackblitz.com/edit/angular-ngrx-update?file=src/app/app.component.css
Later Edit:
As Cristian Traina pointed out in the comments, markForCheck
(from inside the async pipe) doesn't trigger the change detection when it's called from outside angular. As a fix, you can use a zone scheduler in your pipe, or detect where the change comes from and patch that area to trigger a change detection inside angular (since there may be problems in other areas of your app).
https://www.npmjs.com/package/ngx-zone-scheduler?activeTab=readme
public constructor(
@Inject(ZoneScheduler)
private readonly zoneScheduler: SchedulerLike,
) {}
this.loaderOverlay$ = this.store.pipe(
select(selectors.loaderOverlaySelector),
observeOn(this.zoneScheduler),
);
or you can just tick
the appRef
public constructor(
private appRef: ApplicationRef,
) {}
this.loaderOverlay$ = this.store.pipe(
select(selectors.loaderOverlaySelector),
tap(_ => this.appRef.tick()),
);
Upvotes: 5
Reputation: 134
I had a similar issue. By editing an input field, the results were to be fetched from the backend and shown in a list. The list did not get updated unless I hovered over or clicked somewhere else.
I fixed it by adding a manual change detection after fetching the data from the backend.
constructor(private cd: ChangeDetectorRef) {}
fetchData.subscribe(results => {
AddResultsToStore(results);
this.cd.detectChanges();
});
Upvotes: 3
Reputation: 1431
From your example, it seems like the onPush
strategy is not being used correctly. The onPush
strategy means that the component will not necessarily update if its @inputs
are not changing, even if variables are being updated in the component. From what I can gather in your example, the parent component's @input
references are not changing, which means that the change detection is not happening as you expect.
To fix this I would suggest updating the parent component (which is subscribing to the store updates) to either use defaultStrategy
or manually call markForCheck()
as needed to trigger an update.
Upvotes: 3