@ngrx/component が試せるようになっていたので使ってみた
サンプルコード
@ngrx/component とは
- ドキュメントを見ると zone less を実現するためのヘルパーで Angular アプリケーションをより Reactive に書けるようになると書いてある
- 試せる API は Let Directive と Push Pipe の 2 つ
事前準備
- 以下のようなサンプルアプリケーションを用意した(スタイル省略)
import { HttpClient } from '@angular/common/http'; import { Component, OnInit } from '@angular/core'; import { BehaviorSubject, Observable, of } from 'rxjs'; @Component({ selector: 'app-root', template: ` <main> <h2>click event</h2> <section> <div>Message: {{ message$ | async }}</div> <button (click)="showMessage()">Click</button> </section> <h2>Http request</h2> <section> <ng-container *ngIf="user$ | async as user"> <div>{{ user.name }}</div> </ng-container> </section> </main> `, }) export class AppComponent implements OnInit { constructor(private http: HttpClient) {} messageSubject = new BehaviorSubject<string>(''); user$: Observable<any | null> = of(null); get message$() { return this.messageSubject.asObservable(); } ngOnInit() { this.fetchUser(); } // NOTE: click event showMessage() { this.messageSubject.next('Hello world'); } // NOTE: Http request fetchUser() { this.user$ = this.http.get('https://jsonplaceholder.typicode.com/users/1'); } }
- サンプルアプリケーションには 2 つのシナリオを用意している
- ボタンクリックのイベントリスナー
- HTTP リクエストを介したデータの取得
- Angular は通常 NgZone によってこれらを監視し、変更検知をトリガーしている
- NgZone が変更検知を行っているわけではない
- NgZone については公式ドキュメント を見るのがよい
zone-less
- パフォーマンス向上のために NgZone を無効にすることもできる
- しかし、NoopZone にすると変更検知のトリガーがされなくなり view の更新が行われなくなる
- いよいよ @ngrx/component を使っていく
Let Directive
- zone-full と zone-less のどちらのモードでも同じように動作する
*ngIfが falsy のときに描画されない問題も解消できる- observable な値を view にバインドするのに
*ngIfが必要だったがそこの置き換えができる
- observable な値を view にバインドするのに
<section> - <ng-container *ngIf="user$ | async as user"> + <ng-container *ngrxLet="user$ as user"> <div>{{ user.name }}</div> </ng-container> </section>
Push Pipe
- ngrxPush は AsyncPipe の代替であり zone-full と zone-less のどちらのモードでも同じように動作する
- 以下のように置き換えると zone-less でも動作する
<h2>click event</h2> <section> - <div>Message: {{ message$ | async }}</div> + <div>Message: {{ message$ | ngrxPush }}</div> <button (click)="showMessage()">Click</button> </section>
※ NOTE
- 今回のサンプルコードだと async を ngrxPush に置き換えただけだと動作せず、下記のエラーが発生した
ERROR RangeError: Maximum call stack size exceeded
at TapSubscriber._next (tap.js:43)
at TapSubscriber.next (Subscriber.js:49)
at MapSubscriber._next (map.js:35)
at MapSubscriber.next (Subscriber.js:49)
at DistinctUntilChangedSubscriber._next (distinctUntilChanged.js:50)
at DistinctUntilChangedSubscriber.next (Subscriber.js:49)
at Subject.next (Subject.js:39)
at Object.next (component.js:263)
at PushPipe.transform (component.js:389)
at Module.ɵɵpipeBind1 (core.js:36650)
- issue/2488 にもあったが
asObservable()を使っているとテンプレートチェックのたびに新しい参照を返すために発生するとのこと - 今回は下記の変更も加えて回避した
messageSubject = new BehaviorSubject<string>('');
+ _message$ = this.messageSubject.asObservable();
user$: Observable<any | null> = of(null);
get message$() {
- return this.messageSubject.asObservable();
+ return this._message$;
}
ngOnInit() {
感想
ドキュメントにもまだ「実験的」な機能である旨の一文が書いてあるが、zone-less Angular を体験してみるにはとてもよかった。
実際のアプリケーションで zone を外すことを考えたことはまだないが、パフォーマンス懸念やデバッグの難しさから zone を外すことを検討しないといけなくなった場合に代替案があるのはいいと感じた。