はじめに
今回は画像のようなデータベースを例に、RxJSを用いて別コレクションにあるユーザーのデータを合成しながら、記事のデータを取得する例を紹介します。
具体的には、**「articlesコレクション内のauthorId」と「usersコレクション内のuserId」**が一致するドキュメントを合成します。
サンプルコード
大まかな流れはこのようになっています。
- articleコレクションを取得
- usersコレクションを取得
- ドキュメントを合成
getArticles(): Observable<ArticleWithUser[]> {
// 記事のデータを保管しておく空配列を用意
let articles: Article[];
return this.db.collection<Article>(`articles`).valueChanges().pipe(
// 流れて来たarticlesコレクションをswitchMapでusersコレクションに差し替える
switchMap((docs: Article[]) => {
// 差し替えたらarticlesコレクションのデータは取得出来なくなってしまうので保管する
articles = docs;
if (articles.length) {
// filterを使ってauthorIdの重複を無くす
const authorIds: string[] = articles.filter((article, index, self) => {
return self.findIndex(item => article.authorId === item.authorId) === index;
}).map(article => article.authorId);
// 重複なしのIDリストを使ってusersコレクション内のドキュメントを取得
// ドキュメントが流れるObserbableが複数ある状態なので、combineLatestで1つにまとめる
return combineLatest(authorIds.map(userId => {
return this.db.doc<User>(`users/${userId}`).valueChanges();
}));
} else {
// switchMapはObserbableを返す必要があるのでofを使う
return of([]);
}
}),
// 流れてきたuserのドキュメントと、保管しておいた記事のドキュメントを合成する
map((users: User[]) => {
return articles.map(article => {
const result: ArticleWithUser = {
...article,
author: users.find(user => user.id === article.authorId),
};
return result;
});
})
);
}
このコードで登場するRxJSのOperatorsを簡易解説
switchMap: Observableを別のObservableに差し替える
combineLatest: 複数のObservableを1つにまとめる
map: 流れて来た値に何かしら変更を加えて戻す
知らないOperatorsはこのサイトで調べると良いです
https://www.learnrxjs.io/learn-rxjs/operators
switchMap内でauthorIdの重複をfilterで無くしている理由
無駄なリクエストを無くすためです。
重複したままだと、取得した記事のうち100件が同じユーザーの記事だった場合に、99回も無駄にユーザーデータをリクエストすることになってしまいます。
複数のメソッドで使い回す
Firestoreでは、このようにコレクションの絞り込みや、並び替えが出来ます。
return this.db.collection<Article>(`articles`, ref => {
return ref.where('条件').orderBy('条件').limit('数');
}).valueChanges();
実際に記事を取得するシーンでは**「いいね数が多い順で取得」「特定のユーザーの記事だけ取得」**など、条件付きで取得することが多いと思います。
そのような場合**「条件だけが違い、中身はほとんど上記のgetArticlesメソッドと同じ」**というメソッドを量産してしまうのは、DRY原則からして良いコードとは言えません。
解決方法
個別のメソッドでは条件指定だけを行いましょう。
並び替えや絞り込みが完了したコレクションをgetArticlesに渡します。
ここでは、favoriteの数を参照して降順で並び替えた上で6件だけ取得しています。
getPopularArticles(): Observable<ArticleWithUser[]> {
const sorted = this.db.collection<ArticleWithUser>(`articles`, ref => {
return ref.orderBy('favorite', 'desc').limit(6);
});
return this.getArticles(sorted);
}
getArticlesはコレクションを受け取るように変えてあげましょう。
受け取ったsortedを使って、記事とユーザーのデータを合成して返します。
getArticles(sorted: AngularFirestoreCollection<Article>): Observable<ArticleWithUser[]> {
let articles: Article[];
return sorted.valueChanges().pipe(
switchMap((docs: Article[]) => {
articles = docs;
// ...以下省略
#参考サイト
CAMP
https://to.camp/about
Angular × Firebaseを用いたSPA開発を教えているオンラインスクールです。
日本語の情報リソースが少ないAngularの解説動画や記事が載っているため、重宝しています。