ただSPAを作りたいだけなのに。Obervableを監視したいだけなのに。
いくら調べてもいまいち何をやっているのかつかめない。
そんな人のための記事です。
Observable状態管理をするにあたっては2つの入り口があります。
- SPAを作るために状態管理を始めた
- 非同期処理をシンプルに実装したくて始めた
この入り口の違いで、 理解のアプローチは全く異なる。
2からはじめた人は躓かないでしょうが、1から入った人は、ドキュメントを読んでも非同期処理とかストリームとか、SPAとは関係ない話ばかり出てきて混乱しがちです。
SPAとは
シングルページアプリケーションですね。
SPAの肝は、リアクティブ(リアルタイムに値の変更に対して処理が行われる)であるということ。
ページ遷移やボタンでjavascriptを発火しなくても、フォームの値が変更されたら、リアルタイムにその変更を読み取り、処理を実行したい、という用途ですね。
Observableなオブジェクトは、SPAの基礎的な理解があれば、 『監視対象のオブジェクト』という意味で理解できていると思います。
Observableについて、まずはSPAでの用途だけ覚えよう
Observableは、『監視対象』という意味以外に、非同期処理を簡潔に記述する方法である、という側面があります。以下はめちゃめちゃわかりやすい記事です。
なのですが、むしろSPA、アプリケーションで大切なのは同期処理の方ですよね。
最低限のオペレーターと、 非同期であるObservableを同期処理に書き換える、lastValueFromの使いかただけ押さえておけば良いのですが、ここで冒頭の非同期処理の記事ばかりを目にして何の話をしているのかわからなくなってしまう、というのが混乱のもとです。
SPAに関して言えば、 Observableはリアクティブな特性を持つオブジェクト(というと少し語弊はありますが)。
だから、だから、pipeとかtapとか、普通のオブジェクトとは処理の仕方が違う。というくらいの認識があればOKなんです。
subscribeの実装方法
subscribeとは何なのか
Observableを扱うにあたって、1番大切な処理はsubscribeです。
RxJSを使用している場合、 AngularでHTTPリクエストを送信すると、レスポンスとして Observableにラップされたレスポンスオブジェクトが返却されます。
this.http.get<void> ('URL') // Observable<T>オブジェクトが返る
なのですが、上記の処理を書いただけでは、リクエストは実行されません。
Observableには.subscribe()メソッドがあり、これを使用して初めてリクエストが『実行』されます。
this.http.get<void> ('URL').subscribe();
このsubscribeには、3つのメソッドのみが用意されています。特に何も追加の処理が必要ない場合は、記載しなくても問題ありません。
subscribe(
next: nestMethod(), // 後続処理を記述
error: error Method() // エラー時の処理を記述
complete: completeMethod() // 完了時の処理を記述
)
ここまでの仕様から、HTTPリクエストを送信し、レスポンスに対して処理を実行する一番シンプルな実装は以下のような記述になります。
this.http.get ('URL').subscribe (
next: (response: any) => {
console.log(response.paramName);
}
);
次に覚えなくてはいけないのはpipeです。
pipeを挟むと、 中間処理を記述できるようになります。
this.http.get<void>('URL').pipe().subscribe();
Observableオブジェクトに中間処理を挟む場合、pipe()以外の選択肢はありません。 おまじないのように覚えておいてもOKです。
pipeの中ではオペレーターを記述します。オペレーターを使用することで、レスポンスの非同期処理を柔軟に行うことができるようになります。
以下はtap(レスポンスに追加処理を加える)オペレーターと、 catchError(エラーをキャッチする)オペレーターを使用する例です。
this.http.get ('URL').pipe(
tap(res => {
}),
console.log(res.paramName);
catchError(err => {
console.log(err);
})
).subscribe();
使用できるオペレーターはたくさんあります。
tapとmap、catchErrorだけ覚えておけば、 基本的には困らないですが、 filterやcount、reduceなど、知っていれば知っているだけ、簡潔で質の高いコードになります。
機会と時間があればRxJSのHPを見て、積極的に使ってみましょう。
subscribeを使用したObservableの実行は、これだけで出来るようになります。
SPAでのよりシンプルな実装方法
lastValueFromで実行する
ここまで基礎として、subscribeを使用した実行方法を見てきましたが、SPAにおいては、subscribeではない、もっと便利な実行方法があります。
それは、Obserbableオブジェクトに対してlastValueFromを使用する方法です。
lastValueFrom(obs); // obs Obserbableオブジェクト
serviceにビジネスロジックを切り分けることが多いと思うので、 実際には以下のようになるかと思います。
■component.ts // 呼び出し側
lastValueFrom (this.service.sampleFnc());
■service.ts
sampleFnc(): Observable {
rerutn this.http.get('URL') // Observableをsubscribeせずにreturnする。pipeを記述してもOK
}
上記はsubscribeを記述していませんが、リクエストは送信されます。
これだけの違いであれば、subscribeでもどちらでもよい気がしますが、lastValueFromにはもう一つの主たる用途があります。
それは、Observableを実行するだけでなく、 戻り値の型をPromiseに変換してくれることです。
アプリケーションにおいて、同期処理は重要な意味を持ちますが、処理を待つawaitを実装するためには、Promise型にラップされていないといけないので何かと面倒です。
ですが、lastValueFromを使えば、awaitを簡単に使用することができます。
■component.ts // 呼び出し側
async ngOnInit() {
await lastValueFrom(this.service.sampleFnc()); // this.service.sampleFnc()の戻り値はObservable
}
本来、awaitを使用するためには、this.service.sampleFnc()の中では、Promiseをnewして作成して返さないといけません。
lastValueFromであれば、シンプルにObservableオブジェクトとして返却するのみで同期処理が実装できます。
また、subscribeを使用する場合、説明は省略したのですが、メモリの消費を防ぐためにngOnDestroy時にunsubscribeでクローズしなくてはなりません。
lastValueFromはsubscriptionしないので、この処理も不要になります。
まとめると
結局のところSPAの基礎的な挙動は以下を抑えておけばOKです。
- Observable になっているかどうかを見極める
- pipe() の記述と少しのオペレーター
- lastValueFrom
学習のロードマップとしては以下です。
- SPAやりたい
- rxJSを使用してリアクティブに処理を行える (状態管理)
- リアクティブな実装にはObservableを使用する
- Observableは非同期処理を便利に扱える側面がある ←★ここはオプションなので、難しく考えすぎて足踏みしないこと!!
- last ValueFromでObservableをPromiseに変換して、同期処理にする
Appendix
nullを返したい
catchError など、 Observableを返さなければいけないメソッドで、特に何も返したくない、nullを返したい場合は以下のように記述します。
return of(null)
of はオペレーターの一種で、 Observableを返してくれます。
firstValueFromとlastValueFromの違い
firstValueFromは、監視していたオブジェクトの最初の値、lastValueFromは最後の値を取得します。
例えばテキストボックスを作成し、その値を監視 (Observe) するとします。
テキストボックスに英語を入れると0を出力し、 日本語では1を出力するようなフォームを作った場合、テキストボックスに、英語を入力>日本語を入力 と操作するとfirstValueFromでは0が取得され、 lastValueFromでは1が取得されます。
tapとmapの違い
どちらもpipe()内で使用する、レスポンスに追加処理を加える代表的なオペレーターです。
tapは値を返さないとき、mapは値を返すときに使用すると良い、とだけ覚えておけばOKです。
Observableのエラーハンドリング
pipe(catchError())と、subscribe(error: method()) の違い
どちらでもエラーキャッチはできますが、以下の違いがあります。
■pipeでのcatchError
エラーをハンドリングして処理を継続できる。>subscribeの処理も実行される。
catchErrorはObservableをreturnするように強制されます。Observableを返却している以上、以降の処理も実行されることになります。
■subscribeでのerror
終端処理のため、 問答無用で処理は完結します。
※エラー処理をしない場合、 処理が止まってしまうので注意が必要です
pipe (catchError()) の挙動
■service.ts
this.http.get('URL').pipe( // このリクエスト自体がエラーを返す場合
tap(() => {
console.log('てすと 1'); // 実行されない
}),
catchError(() => {
console.log('てすと2'); // 実行される
return of (null); // ★Observableを返してあげれば、 後続処理が実行される
}),
tap(() => {
console.log ('てすと3'); // 実行される
})
).subscribe({
next: () => {
console.log ('てすと4'); // 実行される
},
error: (() => {
console.log('てすと5'); // 実行されない
})
});
■エラーを呼び元にそのまま返したいとき
catchError((err) => {
return throwError(() => err);
})
参考:Angular(RxJS)のエラーハンドリングで使ってるオペレータたち
subscribe (error: method()) の挙動
this.http.get ('URL').pipe( // このリクエスト自体がエラーを返す場合
tap(() => {
console.log ('てすと1'); // 実行されない
})
).subscribe({
next: () => {
console.log('てすと2'); // 実行されない
},
error: (() => {
console.log ('てすと3'); // 実行される
})
});
lastValueFrom呼び出し側でのエラーキャッチ
■component.ts
lastValueFrom(this.service.serviceFunkName()).catch((err) => {
// エラー処理
})
コメント