A First Look at @ngrx/component

John Crowson
ngconf
5 min readApr 11, 2020

--

At NG Conf 2020, NgRx Core Team member Mike Ryan gave a talk titled “State of NgRx”. In it, Mike introduced the newest section of the NgRx family, NgRx View, which includes the @ngrx/component and @ngrx/router libraries.

In this blog, we’ll look at @ngrx/component, which rethinks how change detection works for Angular applications, and improves the handling of RxJS Observables directly in our Angular templates.

Change Detection

Out of the box, Angular 9 uses Zone.js to run change detection. If you are curious as to how Zones work or the drawbacks associated with the library’s use, there are plenty of resources available online.

With NgRx Component, RxJS Observables are used under the hood to determine when to run change detection.

Handling Observable in Templates

Out of the box, most Angular pipes and directives used in templates, like the async pipe, were not built exclusively for Observables.

With NgRx Component, an additional pipe and directive are introduced to make it as ergonomic as possible to use Observables directly in Angular templates.

Let’s see how we can use NgRx Component to eliminate the use of Zones and improve the handling of Observables in Angular templates!

Example App with Zones + AsyncPipe

To illustrate the use of Zones and the async pipe, let’s look at an example project that binds two Observables directly in a template:

  • An Observable<number> (count$) that acts like a counter, emitting an incremented count value every second, starting at zero.
  • An Observable<boolean> (isCountEven$) that maps the above Observable to a boolean value, depending on whether the emitted count is an even or odd number.
<!-- Below uses Zones for change detection -->
<h3>
Counter: {{ count$ | async }}
</h3>
<!-- Want below to show when observable emits for the first time -->
<!-- Unfortunately, below also doesn't show when the isCountEven$ emits a falsy value -->
<p *ngIf="isCountEven$ | async as isCountEven">
The count is {{ isCountEven ? 'even' : 'odd' }}
</p>

Issues with Zones + AsyncPipe Example

The first issue encountered is our use of Zones. Although it might not be visible in a small example, Zones can lead to poor performance as an application scales.

From the NgRx Component Documentation:

Heavy dynamic and interactive UIs suffer from zones change detection a lot and can lead to bad performance or even unusable applications, but the async pipe does not work in zone-less mode.

The second issue is the example’s use of the *ngIf directive to only show the paragraph tag after the isEvenCount$ Observable has emitted a value.

This works because the async pipe emits null before an Observable emits the first value, which is considered falsy by the NgIf directive. However, the NgIf directive is also inadvertently hiding the paragraph tag when the Observable returns other intentionally falsy values, in this case, when isEvenCount$ emits false.

Let’s use NgRx Component to address these issues!

Exit Zones

Although not practical for most existing Angular applications today, we can remove Zones in our small example application. We can remove Zones by commenting out the import of the library in polyfills.ts and adding the ngZone: 'noop' compiler option to our bootstrapModule function in main.ts.

platformBrowserDynamic().bootstrapModule(AppModule, {
ngZone: 'noop'
}).catch(err => console.error(err));

You’ll notice this renders our application without a functioning counter, because the AsyncPipe does not work without Zones.

Enter NgRx Component

We can install @ngrx/component and save it as a dependency using the following command:

npm install github:ngrx/component-builds.git --save

Note that @ngrx/component is an experimental library and is only available through GitHub as of today. Stay tuned for updated @ngrx/component installation steps. Because Stackblitz does not currently support GitHub package dependencies, the following Stackblitz examples have the @ngrx/component source code directly contained in the src folder.

I also had to disable AOT compilation in my angular.json file to get @ngrx/component to work in theexample app.

"aot": false

Finally, let’s declare both the PushPipe and the LetDirective from the NgRx Component library:

import { PushPipe, LetDirective } from '@ngrx/component';@NgModule({
declarations: [
AppComponent,
PushPipe,
LetDirective
],
// ...
})
export class AppModule { }

Now that we have @ngrx/component installed, let’s revisit our example app.

Example App with NgRx Component

Using PushPipe

The PushPipe is a direct replacement for the AsyncPipe and schedules change detection when new Observable values are pushed. The PushPipe works with or without Zones.

In the app.component.html template, we can replace each use of the async pipe directly with the ngrxPush pipe. Our app behavior has now been restored, but without Zones!

<h3>
Counter: {{ count$ | ngrxPush }}
</h3>
<p *ngIf="isCountEven$ | ngrxPush as isCountEven">
The count is {{ isCountEven ? 'even' : 'odd' }}
</p>

Using LetDirective

The LetDirective is a structural directive that works similar to the NgIf directive, except the LetDirective is built specifically to unwrap values from an Observable.

To address the issue of our paragraph tag being hidden when the Observable emits falsy values, we can replace the use of the *ngIf directive with the *ngrxLet directive. Because the LetDirective expects an observable, there is no need to use the AsyncPipe or the PushPipe.

<h3>
Counter: {{ count$ | ngrxPush }}
</h3>
<p *ngrxLet="isCountEven$ as isCountEven">
The count is {{ isCountEven ? 'even' : 'odd' }}
</p>

Because the LetDirective allows falsy values from our Observable, our application is now behaving as intended. 🎉

Conclusion

It is awesome to see NgRx’s commitment to providing primitive reactive helpers to enable fully reactive, fully zone-less applications. I encourage you to watch both Mike Ryan and Brandon Roberts talks at NG Conf 2020 to hear more about what NgRx has in active development.

To read more about @ngrx/component, be sure to check out the work-in-progress documentation available on ngrx.io.

Follow me on Twitter :) @john_crowson

EnterpriseNG is coming November 4th & 5th, 2021.

Come hear top community speakers, experts, leaders, and the Angular team present for 2 stacked days on everything you need to make the most of Angular in your enterprise applications.
Topics will be focused on the following four areas:
• Monorepos
• Micro frontends
• Performance & Scalability
• Maintainability & Quality
Learn more here >> https://enterprise.ng-conf.org/

--

--