LoginSignup
7
5

More than 3 years have passed since last update.

【Angular x Firestore】フィールドの値を元に別のドキュメントを合成して取得する

Last updated at Posted at 2020-03-19

はじめに

今回は画像のようなデータベースを例に、RxJSを用いて別コレクションにあるユーザーのデータを合成しながら、記事のデータを取得する例を紹介します。

具体的には、「articlesコレクション内のauthorId」と「usersコレクション内のuserId」が一致するドキュメントを合成します。

SS 2020-03-17 17.56.03.png

サンプルコード

大まかな流れはこのようになっています。

  1. articleコレクションを取得
  2. usersコレクションを取得
  3. ドキュメントを合成
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の解説動画や記事が載っているため、重宝しています。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5