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

原文は[こちら](https://github.com/angular/angularfire/blob/master/docs/firestore/collections.md)。これをざっくり訳したものです。

# 3. Collections in AngularFirestore

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

## `AngularFirestoreCollection` を利用する

`AngularFirestoreCollection`サービスは、Firestore SDKの[`CollectionReference`](https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference) と [`Query`](https://firebase.google.com/docs/reference/js/firebase.firestore.Query) typesをラップしたものです。
強力なデータ操作・ストリーミングの機能を提供するジェネリックサービスです。
このサービスは、`@Injectable()`として利用できるようになっています。

```ts
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` ）を含みます。

```ts
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を永続化する例

```ts
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システムとの統合時に威力を発揮します。 各`DocumentChangeAction`の`type` プロパティは、ngrx reducers、form states、animation statesなどに使うことが出来ます。

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

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

#### Example

```ts
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

```ts
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

```ts
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`の３つの`DocumentChangeType` があります。各ストリーミングメソッドは、デフォルトで３つともリスンしています。一方で、メソッドの第一引数に指定することにより、特定の1つのイベントのみをリスンするようにすることもできます。

#### Basic example

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

#### Component example

```ts
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に「何が起きたか」を返します。

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

## collectionにdocumentを追加する

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

#### Basic example

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

## 個々のdocumentの操作

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