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 im 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?
Change detection works by detecting common browser events like mouse clicks, HTTP requests, and other types of events, and deciding if the view of each component needs to be updated or not.
But with OnPush strategy, the change detector is only triggered if the data passed on @Input() has a new reference. This is why using immutable objects is preferred, because immutable objects can be modified only by creating a new object reference.
Angular Change Detection Strategy are the methods by which the updates to the component is tracked and component is triggered to Re-render. There are majorly 2 Change Detection Strategy in Angular. We can configure the Change Detection Strategy for the Component inside the Decorator.
The main idea behind the OnPush strategy manifests from the realization that if we treat reference types as immutable objects, we can detect if a value has changed much faster. When a reference type is immutable, this means every time it is updated, the reference on the stack memory will have to change.
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()),
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With