RxJSを活用した
Angularアプリの状態管理設計
2017/8/25 @ BizReach
hashtag #d3_angular
自己紹介
- 西田雅博
- HRMOS事業部
- フロントエンドメイン
- バックエンドもやります
- Angular 4
- Play Framework (Scala)
- React
![](https://s3.amazonaws.com/media-p.slid.es/uploads/518584/images/3909756/ZCbOilUM_400x400.jpg)
contents
- Angularっていいですよね
- 状態ってなに
- NgRxを使わなかった理由
- Savkinの状態管理理論
- RxJSを使った状態管理設計
Angularって
いいですよね
Angularのいいところ
- フルスタックフレームワーク
- 周辺ツールが充実
- Angular-CLI, Language Support, Material (Component Development Kit)
- RxJSでの非同期・イベントハンドリング
- + asyncパイプの利便性
- テスタビリティの高さ
- ドキュメントが充実 angular.io
Angularのいいところ
- PWAサポートも来るかも?
- Semantic Versioningと定期リリースによる安心感
- ionicでアプリもカバー
- ng-japanのSlackチャンネルで質問するとほとんど回答いただける🙏
- などなど
弊社とAngular
- AngularJS 1.x からのリプレイスを進行中
- 他のプロダクトでも使っている
- 「Angular 4」で謎のGoogle検索1位
![](https://s3.amazonaws.com/media-p.slid.es/uploads/518584/images/4071910/__________2017-08-25_8.10.46.png)
Angularのよくなさそうなところ
- 学習コストの高さ
- Zone.jsなど裏側はすごいことになってそう
- など・・・?
- (状態管理のレールがない)
- 今日の本題
- よくないとも言えない
- 下手なオレオレになってしまう危険?
- NgRxが半公式?
状態管理
- 状態をどう管理するかはJS界隈でよく議論になる
- Fluxアーキテクチャを各FWで実装してる状況
- The Elm Architectureすげ〜
状態って?
- ユーザーがフォームに入力した文字列
- モーダルが開いている・閉じている
- ユーザーが開いているURL
- サーバーサイドから取得したJSON
- などなど??
Savkinの状態管理理論
と勝手に呼んでいる記事
Managing State in Angular Applications
https://blog.nrwl.io/managing-state-in-angular-applications-22b75ef5625f
Savkin先生
![](https://s3.amazonaws.com/media-p.slid.es/uploads/518584/images/4064897/__________2017-08-23_8.00.23.png)
先生曰く
(記事の要約)
- フロントエンドの状態を管理するのは難しい
- 状態を6つに分類する
- 状態のうち、他と同期すべきものがいくつかある
- その実装をミスってバグを生むことが多い
- Reduxを使うと良い
- NgRxも良い
- 今回は状態の分類だけご紹介
状態を6つに分類する
- Server state
- Persistent state
- URL state
- Client state
- Transient client state
- Local UI state
Server State
- バックエンドのサーバーが保持する状態
- REST APIを通じて取得することが多い
Persistent state
- クライアントサイドに保存したServer state
- Server stateのキャッシュとも言える
- より良いUIのために楽観的更新をここで担ったりする
URL state
- ユーザーが現在開いているURL
Client state
- サーバーには保存しない状態
- 例: Todoリストの未完了だけ表示するフィルタ
- Client stateをURLに反映するのは良いプラクティス
- adwd-todo.com/user123/todos?status=done
Transient client state
- URLには反映しないクライアントの状態
- 例: YouTubeの再生時間
- URLには表示されないが、次に同じ動画を開くとその再生時間から再開する
- URLには反映されないので、そのURLを他人が開くと最初から再生する
- Cookieでやってるっぽい
Local UI State
- ボタンの色
- メニューが展開されているか
- など
状態の同期
- 6種類の状態のうち同期すべきものがある
- Server state <=> Persistent state
- URL state <=> Client state
- (Persistent state <=> URL state)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/518584/images/4071879/0-I3jAfhOZPcF1Mjil.png)
NgRx
NgRx
- Reduxインスパイア の状態管理ライブラリ
- store, effects, router-storeなど
- ちょっと前にバージョンが2.xから4.xに飛んだ
- Angularのバージョンと合わせるため?
- Angularチームのコミットも多く半公式?
- おそらくはAngularで大規模なアプリを作る場合はこれを使うのがセオリーになっていきそう
NgRxを使わない理由
(個人的な)
- ボイラープレートが多すぎる
- リッチにアプリケーションを書けるAngularにReduxの素朴なスタイルが合わない気がする
- Reduxの非同期・副作用を隔離するという動機もAngularではあまり意味がないと思う
- 個人的には1年以上React/Reduxやってたので、ものすごく飽きている 🙄
- Reduxスタイルではやりにくいケースも
- 実際にNgRxをちゃんと使ったわけではないので間違った判断かもしれない
どう状態管理するか?
- Savkin理論をベースに考える
- Persistent stateにはBehaviorSubjectが使える
- このアイデアはいくつか既存のものがある
- Angular Model Pattern
- Observable Data Service
- RxJS Real World (ng-japan 2017)
- このアイデアはいくつか既存のものがある
Persistent stateの実装
- バックエンドが返したJSONをストリームするBehaviorSubjectをつくる
![](https://s3.amazonaws.com/media-p.slid.es/uploads/518584/images/4065044/super_businessman.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/518584/images/4065046/smartphone_businessman_stand.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/518584/images/4065047/smartphone_businessman_stand_smile.png)
BehaviorSubject
ComponentA
ComponentB
GET /users
PUT /users/123
DELETE /users/456
@Injectable()
export class UserHttpService {
private _user: BehaviorSubject<User>;
private user: Observable<User>;
constructor(private http: HttpClient) { }
fetchUser(): Observable<User> {
return this.http.get<User>('/api/user')
.switchMap((user: User) => {
if (this._user) {
this._user.next(user);
return this.user;
} else {
this._user = new BehaviorSubject(user);
this.user = this._user.asObservable();
return this.user;
}
});
}
getUser(): Observable<User> {
return this.user || this.fetchUser();
}
updateUser(user: Partial<User>) {
return this.http.put('/api/corporate', user)
.do(() => {
if (this._user) {
const current = this._user.getValue();
this._user.next({ ...current, ...user });
}
});
}
}
データをキャッシュしマルチキャストする
BehaviorSubject
外側からいじられたくないのでObservableに変換
HTTPから得たデータの後続にswitchMapでBehaviorSubjectをくっつけて更新が流れてくるようにする
既にfetch済みならそれでいいですというユースケースに対応
getValue()をつかって楽観的更新
URL, Client state
- URL state, Client stateも同期すべき
- URLをstateのsourceとする
- 変数でClient stateを表現しない
// inputにidを入力するとURLが/foo?id=123のように変わり、
// そのidのデータを取得して表示するコンポーネント
@Component({
template: `
<input type="text" #input placeholder="enter id"/>
<button (click)="onClick(input.value)">submit</button>
<p>{{result$ | async}}<p>`
})
export class FooComponent implements OnInit {
result$: Observable<string>;
constructor(
private route: ActivatedRoute,
private router: Router,
private service: FooService,
) { }
ngOnInit() {
// URLを状態のソースとして、変更を監視する
this.result$ = this.route.queryParams.switchMap((params: Params) =>
this.service.getFoo(params['id'])
);
}
onClick(id: string) {
// 状態の変更はURLに対して行う
this.router.navigateByUrl(`/foo?id=${id}`);
}
}
アーキテクチャ概要
Persistent state
Server state
Transient client state
Cookie, LocalStorage, ..
Client state
URL state
![](https://s3.amazonaws.com/media-p.slid.es/uploads/518584/images/4068141/business_senryaku_sakuryaku_man.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/518584/images/4068143/syukatsu_jiko_appeal_woman.png)
Service
Component
同期
同期
同期
リソースを管理してSubjectで公開する
ActivatedRoute, Routerを使ってURLに状態を反映・監視
Local UI state
リソースと画面の状態からあれこれ計算する
ドメインロジック
表示に専念する
難しいことは下のServiceに任せる
アーキテクチャ概要
Persistent state
Server state
Transient client state
Cookie, LocalStorage, ..
Client state
URL state
![](https://s3.amazonaws.com/media-p.slid.es/uploads/518584/images/4068141/business_senryaku_sakuryaku_man.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/518584/images/4068143/syukatsu_jiko_appeal_woman.png)
Service
Component
同期
同期
同期
Local UI state
Presentation
Domain logic
Resource management
アーキテクチャ概要
- Savkin状態理論に基づき状態を分類
- RxJSを使って状態を同期
- Server <=> Persistent state
- BehaviorSubjectを使う
- URL <=> Client state
- URLに状態の変化を反映する
- URLの変更を監視する
- (Persistent <=> URL state)
- URLの変更をトリガにPersistentの状態を更新
- Server <=> Persistent state
アーキテクチャ概要
- 関心に基づき階層化
- Component (Local UI state)
- 表示に集中
- 下位のサービスからデータを受け取る・イベントを渡す
- DomainService
- コンポーネントが扱うドメインロジックが集中、一番複雑
- ここに集めてテストしやすくなるはず?
- ComponentやHttpも登場しないので、Angularに非依存のIsolated Testができるはず
- コンポーネントが扱うドメインロジックが集中、一番複雑
- Resources (Persistent, Transient client state)
- リソースの管理に集中
- Component (Local UI state)
まとめ
- Angularでの状態管理
- Savkin先生の状態の分類
- Angular Model Pattern, Observable data serviceなどと呼ばれているSubject活用パターン
- Savkin理論+Subject活用でNgRx使わなくてもいけそう?
おわり
🙏
RxJSを活用したAngularアプリの状態管理設計
By adwd
RxJSを活用したAngularアプリの状態管理設計
Angular state-management with RxJS
- 14,881