Angular change detection not detecting changes only in prod

1.6k views Asked by At

I've got a strange problem that I hope is just a simple fix on my end. The change detection in Angular is not working only in production.

I've built a fairly complex app using Angular 7 and ngrx as state management. There are 2 main modules that are lazy loaded base on the route. The change detection strategy is OnPush and 99% of the app follows a pattern of data comes from server via ngrx effects to stateful components that then push the data into "dummy" presentational components via async pipe. Example:

this.loading$ = this.store.pipe(
  select(selector.isLoading),
  takeUntil(this.unsubscribe$)
);
<app-loading [loading]="loading$ | async"></app-loading>

On ng serve and ng serve --prod everything works as expected and there are no issues. However after running ng build --prod and pushing the js files into our .net solution change detection ceases to work.

What do I mean by "ceases to work"? I mean it does not change any model or components unless a DOM event has taken place.

For example using the code shown as above there is a loading spinner that displays until data comes from the server. In production the spinner sits on the screen until you randomly click. Then the app refreshes and the data is displayed. This issue continues throughout the app. That is, this issue is not just happening there.

The app is loaded in an .cshtml file in a .Net solution like this:

<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="polyfills.js"></script>
<script type="text/javascript" src="main.js"></script>

The lazy loaded modules are within the folder of the .cshtml file and are showing no errors so I must assume everything is wired up correctly.

Again this is only in the prod bundle. Not in dev or serve or prod mode serve.

2

There are 2 answers

2
s.Lawrenz On

This ended up being a very weird situation with the rxjs from operator.

The application uses signalR as the API between the client and server. Most of the interactions with the hub is a jquery promise. I used the from operator to return the data.

return from(this.proxy.invoke(...args));

This worked find in development for reasons I cannot figure out. But the observer would not complete in production so even though state would update, the view was still waiting for the observer to complete.

I updated the code to return an observable on connection.

private async performInvoke(...argu): Promise {
  return await this.proxy.invoke(...argu);
}

invoke(...argu): Observable<any> {
  return new Observable(o =>
    const pInvoke = async () => {
      o.next(await this.performInvoke(...argu));
      o.complete();
    }
}

I'm simplifying here so if you find this make sure to add try catches and other error protections.

0
David Votrubec On

For those facing similar issue with unreliable ChangeDetection:

I have experienced similar problem which only happened in customized Embedded Chromium.

The ChangeDetection always worked correctly on localhost environment, but was not reliable in embedded Chromium accessed via VNC. Even when the binaries were exactly the same, the app behaved differently, so it must have been caused by something in the browser.

The hacky solution was to force change detection via setInterval, like this:

// AppComponent
private cdInterval = undefined;

ngOnInit() {
 this.cdInterval = setInterval(() => {
  this.cd.detectChanges();
}, 50);

ngOnDestroy() {
 if (this.cdInterval) {
  clearInterval(this.cdInterval);
 }
}

This is obviously far from ideal, but at least the UI stays in sync.

For the record: I have tried all I could think of:

  • async pipe
  • observables
  • signals
  • forcing cd.detectChanges() for each variable change

Calling cd.detectChanges() for each subscription would work, but would not scale, so I have chose to call it in interval.

I would be more than happy for better solution.