Help us understand the problem. What is going on with this article?

Collections in AngularFirestore / AngularFirestoreでのコレクション

原文はこちら。これをざっくり訳したものです。

3. Collections in AngularFirestore

Cloud Firestoreは、ドキュメント指向のNoSQLデータベースです。SQLデータベースと異なり、テーブルや行といった概念がありません。その代わりに、documentsというものにデータを保存し、それらが集まってcollectionsという形で保持されます。各documentは、key-valueペアが保持され、Cloud Firestoreは、小さなdocumentからなる巨大なcollectionを保存することに最適化されています。

AngularFirestoreCollection を利用する

AngularFirestoreCollectionサービスは、Firestore SDKのCollectionReferenceQuery typesをラップしたものです。
強力なデータ操作・ストリーミングの機能を提供するジェネリックサービスです。
このサービスは、@Injectable()として利用できるようになっています。

import { Component } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import 'firebase/firestore';

export interface Item { name: string; }

@Component({
  selector: 'app-root',
  template: `
    <ul>
      <li *ngFor="let item of items | async">
        {{ item.name }}
      </li>
    </ul>
  `
})
export class AppComponent {
  private itemsCollection: AngularFirestoreCollection<Item>;
  items: Observable<Item[]>;
  constructor(private afs: AngularFirestore) {
    this.itemsCollection = afs.collection<Item>('items');
    this.items = this.itemsCollection.valueChanges();
  }
  addItem(item: Item) {
    this.itemsCollection.add(item);
  }
}

AngularFirestoreCollection は、collectionストリームの作成や基底となるcollectionでのデータ操作の実行に用います。

DocumentChangeAction タイプ

valueChanges() を除き、各ストリーミングのメソッドは、DocumentChangeAction[] からなるObservableを返します。

DocumentChangeActionは、type プロパティと payload プロパティを持っています。type プロパティは、いつ何の DocumentChangeType 操作(added, modified, removed)が発生したかを表します。payload プロパティは、変更に関するメタデータと doc プロパティ(これは DocumentSnapshot )を含みます。

interface DocumentChangeAction {
  //'added' | 'modified' | 'removed';
  type: DocumentChangeType;
  payload: DocumentChange;
}

interface DocumentChange {
  type: DocumentChangeType;
  doc: DocumentSnapshot;
  oldIndex: number;
  newIndex: number;
}

interface DocumentSnapshot {
  exists: boolean;
  ref: DocumentReference;
  id: string;
  metadata: SnapshotMetadata;
  data(): DocumentData;
  get(fieldPath: string): any;
}

collection dataをストリーミングする

Firesotreからのcollectionデータをストリーミングする方法は、複数あります。

valueChanges({idField?: string}) メソッド

What is it? - 現在のcollectionの状態です。JSONオブジェクトからなる配列としてObservableを返します。Snapshotのメタデータは含まれず、document データだけが含まれています。 オプションとして、 idField キーを文字列型として渡すことができます。その場合、 idField によって指定されたプロパティにdocument IDがマップされたJSONオブジェクトが返ってきます。

Why would you use it? - 単にデータのリストが欲しい場合など。返ってくる配列にはメタデータが含まれないため、Viewへのレンダリングがシンプルになります。

When would you not use it? - 配列よりも複雑な構成が必要な場合には、使うべきではないです。

Best practices - 単一のページにデータを表示させる際に使いましょう。シンプルかつ効果的です。より複雑になりそうな場合には、.snapshotChanges() を使いましょう。

Document IDを永続化する例

import { Component } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import 'firebase/firestore';

export interface Item { id: string; name: string; }

@Component({
  selector: 'app-root',
  template: `
    <ul>
      <li *ngFor="let item of items | async">
        {{ item.name }}
      </li>
    </ul>
  `
})
export class AppComponent {
  private itemsCollection: AngularFirestoreCollection<Item>;
  items: Observable<Item[]>;
  constructor(private readonly afs: AngularFirestore) {
    this.itemsCollection = afs.collection<Item>('items');
    // .valueChanges() はシンプルで、メタデータなしのJSONデータを返す
    // doc.id() を使う場合、自分で永続化をするか、代わりに.snapshotChanges()を使う必要がある
    // idをvaluleChanges()で永続化する例は、下記のaddItem()メソッドを参照
    this.items = this.itemsCollection.valueChanges();
  }
  addItem(name: string) {
    // Persist a document id
    const id = this.afs.createId();
    const item: Item = { id, name };
    this.itemsCollection.doc(id).set(item);
  }
}

snapshotChanges() メソッド

What is it? - 現在のコレクションの状態です。DocumentChangeAction[] からなるObservableを返します。

Why would you use it? - データのリストがほしいが、同時に関係するメタデータも保持しておきたい場合に利用します。メタデータは、基底となるDocumentReference、document id、単一のdocumentの配列indexを含んでいます。documentのidにより、データ操作を簡単にすることができます。 このメソッドは、type プロパティにより、ngrx、forms、animationsなどのその他のAngularシステムとの統合時に威力を発揮します。 各DocumentChangeActiontype プロパティは、ngrx reducers、form states、animation statesなどに使うことが出来ます。

When would you not use it? - 単一の配列よりより複雑な構造、または発生時に変更するプロセスが必要な場合には、向いていません。配列は、Firestore内の変更と同期されます(リモート・ローカル両方)。

Best practices - .snapshotChanges()で得たデータを変更する(=transformする)のに、observableオペレーターを使います。テンプレート側にはDocumentChangeAction[]は返さないようにします(下記参照)。

Example

import { Component } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import 'firebase/firestore';

export interface Shirt { name: string; price: number; }
export interface ShirtId extends Shirt { id: string; }

@Component({
  selector: 'app-root',
  template: `
    <ul>
      <li *ngFor="let shirt of shirts | async">
        {{ shirt.name }} is {{ shirt.price }}
      </li>
    </ul>
  `
})
export class AppComponent {
  private shirtCollection: AngularFirestoreCollection<Shirt>;
  shirts: Observable<ShirtId[]>;
  constructor(private readonly afs: AngularFirestore) {
    this.shirtCollection = afs.collection<Shirt>('shirts');
    // .snapshotChanges() は DocumentChangeAction[] を返すが、これにはそれぞれの変更で「何が起こったか」の情報が含まれる
    // 各情報を利用したい場合には、mapオペレーターを利用する
    this.shirts = this.shirtCollection.snapshotChanges().pipe(
      map(actions => actions.map(a => {
        const data = a.payload.doc.data() as Shirt;
        const id = a.payload.doc.id;
        return { id, ...data };
      }))
    );
  }
}

stateChanges() メソッド

What is it? - DocumentChangeAction[] として、直近の変更をObservableとして返します。

Why would you use it? - 下記の例では、クエリ発行順にソートされた配列を返しています。stateChanges() は、クエリ順を同期するのではなく、イベントが発生した順に値を取得します。
Reducerメソッドで独自のデータ構造を構築できるため、ngrxとのインテグレーションに適しています。

When would you not use it? - When you just need a list of data. This is a more advanced usage of AngularFirestore.

Example

import { Component } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import 'firebase/firestore';

export interface AccountDeposit { description: string; amount: number; }
export interface AccountDepoistId extends AccountDeposit { id: string; }

@Component({
  selector: 'app-root',
  template: `
    <ul>
      <li *ngFor="let deposit of deposits | async">
        {{ deposit.description }} for {{ deposit.amount }}
      </li>
    </ul>
  `
})
export class AppComponent {
  private depositCollection: AngularFirestoreCollection<AccountDeposit>;
  deposits: Observable<AccountDepositId[]>;
  constructor(private readonly afs: AngularFirestore) {
    this.depositCollection = afs.collection<AccountDeposit>('deposits');
    this.deposits = this.depositCollection.stateChanges(['added']).pipe(
      map(actions => actions.map(a => {
        const data = a.payload.doc.data() as AccountDeposit;
        const id = a.payload.doc.id;
        return { id, ...data };
      }))
    );
  }
}

auditTrail() メソッド

What is it? - DocumentChangeAction[] として、直近の変更をObservableとして返します。stateChanges() に似ていますが、それまでのイベントを配列として保持します。

Why would you use it? - このメソッドは、一時的ではないという点を除き、stateChanges() と同じです。配列内の各変更が発生すると値を収集します。これは、アプリケーションの状態全体を再現する必要があるケースにおいて、ngrxと統合するのに便利です。またデバッグにも使うことが出来ます。afs.collection('items').auditTrail().subscribe(console.log) と書くだけで、発生したイベントすべてをコンソールで確認できます。

When would you not use it? -データのリストが欲しい場合。auditTrailは、AngularFirestoreのより高度な使用法です。

Example

import { Component } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import 'firebase/firestore';

export interface AccountLogItem { description: string; amount: number; }
export interface AccountLogItemId extends AccountLogItem { id: string; }

@Component({
  selector: 'app-root',
  template: `
    <ul>
      <li *ngFor="let log of accountLogs | async">
        {{ log.description }} for {{ log.amount }}
      </li>
    </ul>
  `
})
export class AppComponent {
  private accountLogCollection: AngularFirestoreCollection<AccountLogItem>;
  accountLogs: Observable<AccountLogItemId[]>;
  constructor(private readonly afs: AngularFirestore) {
    this.accountLogCollection = afs.collection<AccountLogItem>('accountLog');
    this.accountLogs = this.accountLogCollection.auditTrail().pipe(
      map(actions => actions.map(a => {
        const data = a.payload.doc.data() as AccountLogItem;
        const id = a.payload.doc.id;
        return { id, ...data };
      }))
    );
  }
}

eventを制限する

Firestoreには、added, removed, modifiedの3つのDocumentChangeType があります。各ストリーミングメソッドは、デフォルトで3つともリスンしています。一方で、メソッドの第一引数に指定することにより、特定の1つのイベントのみをリスンするようにすることもできます。

Basic example

  constructor(private afs: AngularFirestore): {
    this.itemsCollection = afs.collection<Item>('items');
    this.items = this.itemsCollection.snapshotChanges(['added', 'removed']);
  }

Component example

import { Component } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import 'firebase/firestore';

@Component({
  selector: 'app-root',
  template: `
    <ul>
      <li *ngFor="let item of items | async">
        {{ item.name }}
      </li>
    </ul>
  `
})
export class AppComponent {
  private itemsCollection: AngularFirestoreCollection<Item>;
  items: Observable<Item[]>;
  constructor(private afs: AngularFirestore) {
    this.itemsCollection = afs.collection<Item>('items');
    this.items = this.itemsCollection.valueChanges(['added', 'removed']);
  }
}

State based vs. action based

これらのメソッドは、2つのカテゴリに分類されます。
State based メソッドは、collectionの状態を"as-is"で返します。一方で、action based メソッドは、collectionに「何が起きたか」を返します。

例えば、ユーザーがリストの中の3つめのアイテムを更新するとき、State based メソッドでは .valueChanges() がcollectionの中の3つめのアイテムを更新し、JSON配列を返します。これはどのように This is how your state looks.

collectionにdocumentを追加する

生成されたidをもつ新しいdocumentをcollectionに追加する場合、add() メソッドを使います。このメソッドは、ジェネリクスで指定された型を利用して、、その型を検証します。

Basic example

  constructor(private afs: AngularFirestore): {
    const shirtsCollection = afs.collection<Item>('tshirts');
    shirtsCollection.add({ name: 'item', price: 10 });
  }

個々のdocumentの操作

個々のdocumentを取得、更新、または削除するには、 doc()メソッドを使用できます。このメソッドは、ストリーミング、更新、削除のメソッドを提供する AngularFirestoreDocumentを返します。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした