cup_of
cup_of

Reputation: 6687

Odd change detection behavior Angular 2+

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

Answers (3)

Andrei Tătar
Andrei Tătar

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

Ken Bostoen
Ken Bostoen

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

JusMalcolm
JusMalcolm

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

Related Questions