NgRx 9: Introducing strictActionWithinNgZone runtime check

Stephen Cooper - Mar 10 '20 - - Dev Community

NgRx 9 brings us a brand new runtime check: strictActionWithinNgZone. If enabled, this check will highlight any Action that is running outside of NgZone, enabling faster resolution of potential change detection issues.

Why enable strictActionWithinNgZone?

NgZone is used by Angular to manage change detection. If an Action is running outside of NgZone, any changes it causes, will not be reflected in the rendered output. i.e your user will have a stale view.

For example, if your action is updating a counter, but running outside of NgZone, then the rendered value will not change despite the value changing in your Typescript code. The user will see a stale value until another event triggers change detection.

This can lead to odd behaviour in your application which can be very difficult to debug. See my previous post which inspired this runtime check or this GitHub issue.

The main challenge with this type of bug is identifying it in the first place. Often there are many components triggering change detection so that the buggy component never gets the chance to become stale. However, if you then decide to improve the performance of your application, by reducing change detection, you may suddenly have stale data to deal with!

This is where the strictActionWithinNgZone runtime check helps you out. It immediately pinpoints actions that are running outside NgZone enabling you to fix these issues in development. In many cases, you may not have realised that you had a potential bug in your component!

How to use strictActionWithinNgZone

The strictActionWithinNgZone check is opt-in, (there are valid use cases for running actions outside of NgZone). To enable the check, add it to your runtimeChecks when importing your root StoreModule.

@NgModule({
  imports: [
    StoreModule.forRoot(reducers, {
      runtimeChecks: {
        strictActionWithinNgZone: true
      },
    }),
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

With this enabled, if there is an action, say [Counter] Increment that is running outside of NgZone you will get the following error along with a link to the docs.

Action '[Counter] Increment' running outside NgZone. 
Enter fullscreen mode Exit fullscreen mode

What to do if you see the error?

Now that the action is identified you should locate the code where it was dispatched. I would recommend placing a breakpoint on the dispatch location and then trace the call stack back to the source event. It is worth noting that any Effects triggered by the action will also run outside of NgZone and all their subsequent actions too!

It is very important to trace back to the source event otherwise you may not completely remove the potential change detection issues.

The most likely cause of this will be an external component that is not Angular specific. In my experience, I have run into this where an app used jQuery to capture a Bootstrap event and another time within the error call of a web socket handler.

With the source event identified you can then wrap the call within the ngZone.run callback to ensure that NgZone is aware of this event stack. With this change made the runtime check will no longer error as the action is now running within NgZone.

import { NgZone } from '@angular/core';

    constructor(private store: Store<object>, private ngZone: NgZone) {}

    update(){        
        this.ngZone.run(() => {
            // Bring event back inside Angular's zone
            this.store.dispatch(Increment());
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

NgRx runtime checks

As a reminder here are all the runtime checks that are available to help us avoid common pitfalls when writing our applications. NgRx ships with five built-in runtime checks and from v9 the immutability checks are on by default.

For all the other features in version 9 check out the official release post.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .