Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 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?

like image 777
cup_of Avatar asked Jan 27 '19 07:01

cup_of


People also ask

How does angular 2 detect changes?

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.

What triggers OnPush change detection?

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.

What are the different change detection strategies in Angular?

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.

What is OnPush strategy in Angular?

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.


1 Answers

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()),
);
like image 120
Andrei Tătar Avatar answered Nov 14 '22 06:11

Andrei Tătar