|
| 1 | +# 3. Collections in AngularFirestore |
| 2 | + |
| 3 | +> Cloud Firestore is a NoSQL, document-oriented database. Unlike a SQL database, there are no tables or rows. Instead, you store data in *documents*, which are organized into *collections*. |
| 4 | +Each *document* contains a set of key-value pairs. Cloud Firestore is optimized for storing large collections of small documents. |
| 5 | + |
| 6 | +## Using `AngularFirestoreCollection` |
| 7 | + |
| 8 | +The `AngularFirestoreCollection` service is a wrapper around the native Firestore SDK's [`CollectionReference`](https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference) and [`Query`](https://firebase.google.com/docs/reference/js/firebase.firestore.Query) types. It is a generic service that provides you with a strongly typed set of methods for manipulating and streaming data. This service is designed for use as an `@Injectable()`. |
| 9 | + |
| 10 | +```ts |
| 11 | +import { Component } from '@angular/core'; |
| 12 | +import { AngularFirestore, AngularFirestoreCollection } from 'angularfire2/firestore'; |
| 13 | +import { Observable } from 'rxjs/Observable'; |
| 14 | + |
| 15 | +@Component({ |
| 16 | + selector: 'app-root', |
| 17 | + template: ` |
| 18 | + <ul> |
| 19 | + <li *ngFor="let item of items | async"> |
| 20 | + {{ item.name }} |
| 21 | + </li> |
| 22 | + </ul> |
| 23 | + ` |
| 24 | +}) |
| 25 | +export class AppComponent { |
| 26 | + private itemsCollection: AngularFirestoreCollection<Item>; |
| 27 | + items: Observable<Item[]>; |
| 28 | + constructor(private afs: AngularFirestore) { |
| 29 | + this.itemsCollection = afs.collection<Item>('items'); |
| 30 | + this.items = this.itemsCollection.valueChanges(); |
| 31 | + } |
| 32 | + addItem(item: Item) { |
| 33 | + this.itemsCollection.add(item); |
| 34 | + } |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +The `AngularFirestoreCollection` is service you use to create streams of the collection and perform data operations on the underyling collection. |
| 39 | + |
| 40 | +### The `DocumentChangeAction` type |
| 41 | + |
| 42 | +With the exception of the `valueChanges()`, each streaming method returns an Observable of `DocumentChangeAction[]`. |
| 43 | + |
| 44 | +A `DocumentChangeAction` gives you the `type` and `payload` properties. The `type` tells when what `DocumentChangeType` operation occured (`added`, `modified`, `removed`). The `payload` property is a `DocumentChange` which provides you important metdata about the change and a `doc` property which is the `DocumentSnapshot`. |
| 45 | + |
| 46 | +```ts |
| 47 | +interface DocumentChangeAction { |
| 48 | + //'added' | 'modified' | 'removed'; |
| 49 | + type: DocumentChangeType; |
| 50 | + payload: DocumentChange; |
| 51 | +} |
| 52 | + |
| 53 | +interface DocumentChange { |
| 54 | + type: DocumentChangeType; |
| 55 | + doc: DocumentSnapshot; |
| 56 | + oldIndex: number; |
| 57 | + newIndex: number; |
| 58 | +} |
| 59 | + |
| 60 | +interface DocumentSnapshot { |
| 61 | + exists: boolean; |
| 62 | + ref: DocumentReference; |
| 63 | + id: string; |
| 64 | + metadata: SnapshotMetadata; |
| 65 | + data(): DocumentData; |
| 66 | + get(fieldPath: string): any; |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +## Streaming collection data |
| 71 | + |
| 72 | +There are multiple ways of streaming collection data from Firestore. |
| 73 | + |
| 74 | +### `valueChanges()` |
| 75 | +**What is it?** - Returns an Observable of data as a synchronized array of JSON objects. All Snapshot metadata is stripped and just the method provides only the data. |
| 76 | + |
| 77 | +**Why would you use it?** - When you just need a list of data. No document metadata is attached to the resulting array which makes it simple to render to a view. |
| 78 | + |
| 79 | +**When would you not use it?** - When you need a more complex data structure than an array or you need the `id` of each document to use data manipulation metods. This method assumes you either are saving the `id` to the document data or using a "readonly" approach. |
| 80 | + |
| 81 | +### `snapshotChanges()` |
| 82 | +**What is it?** - Returns an Observable of data as a synchronized array of `DocumentChangeAction[]`. |
| 83 | + |
| 84 | +**Why would you use it?** - When you need a list of data but also want to keep around metadata. Metadata provides you the underyling `DocumentReference`, document id, and array index of the single document. Having the document's id around makes it easier to use data manipulation methods. This method gives you more horsepower with other Angular integrations such as ngrx, forms, and animations due to the `type` property. The `type` property on each `DocumentChangeAction` is useful for ngrx reducers, form states, and animation states. |
| 85 | + |
| 86 | +**When would you not use it?** - When you need a more complex data structure than an array or if you need to process changes as they occur. This array is synchronized with the remote and local changes in Firestore. |
| 87 | + |
| 88 | +### `stateChanges()` |
| 89 | +**What is it?** - Returns an Observable of the most recent changes as a `DocumentChangeAction[]`. |
| 90 | + |
| 91 | +**Why would you use it?** - The above methods return a synchronized array sorted in query order. `stateChanges()` emits changes as they occur rather than syncing the query order. This works well for ngrx integrations as you can build your own data structure in your reducer methods. |
| 92 | + |
| 93 | +**When would you not use it?** - When you just need a list of data. This is a more advanced usage of AngularFirestore. |
| 94 | + |
| 95 | +### `auditTrail()` |
| 96 | +**What is it?** - Returns an Observable of `DocumentChangeAction[]` as they occur. Similar to `stateChanges()`, but instead it keeps around the trail of events as an array. |
| 97 | + |
| 98 | +**Why would you use it?** - This method is like `stateChanges()` except it is not ephemeral. It collects each change in an array as they occur. This is useful for ngrx integrations where you need to replay the entire state of an application. This also works as a great debugging tool for all applications. You can simple write `afs.collection('items').auditTrail().subscribe(console.log)` and check the events in the console as they occur. |
| 99 | + |
| 100 | +**When would you not use it?** - When you just need a list of data. This is a more advanced usage of AngularFirestore. |
| 101 | + |
| 102 | +### Limiting events |
| 103 | + |
| 104 | +There are three `DocumentChangeType`s in Firestore: `added`, `removed`, and `moved`. Each streaming method listens to all three by default. However, your site may only be intrested in one of these events. You can specify which events you'd like to use through the first parameter of each method: |
| 105 | + |
| 106 | +#### Basic smaple |
| 107 | +```ts |
| 108 | + constructor(private afs: AngularFirestore): { |
| 109 | + this.itemsCollection = afs.collection<Item>('items'); |
| 110 | + this.items = this.itemsCollection.snapshotChanges(['added', 'removed']); |
| 111 | + } |
| 112 | +``` |
| 113 | + |
| 114 | +#### Component Sample |
| 115 | +```ts |
| 116 | +import { Component } from '@angular/core'; |
| 117 | +import { AngularFirestore, AngularFirestoreCollection } from 'angularfire2/firestore'; |
| 118 | +import { Observable } from 'rxjs/Observable'; |
| 119 | + |
| 120 | +@Component({ |
| 121 | + selector: 'app-root', |
| 122 | + template: ` |
| 123 | + <ul> |
| 124 | + <li *ngFor="let item of items | async"> |
| 125 | + {{ item.name }} |
| 126 | + </li> |
| 127 | + </ul> |
| 128 | + ` |
| 129 | +}) |
| 130 | +export class AppComponent { |
| 131 | + private itemsCollection: AngularFirestoreCollection<Item>; |
| 132 | + items: Observable<Item[]>; |
| 133 | + constructor(private afs: AngularFirestore) { |
| 134 | + this.itemsCollection = afs.collection<Item>('items'); |
| 135 | + this.items = this.itemsCollection.valueChanges(); |
| 136 | + } |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +## Adding documents to a collection |
| 141 | + |
| 142 | +To add a new document to a collection with a generated id use the `add()` method. This method uses the type provided by the generic class to validate it's type structure. |
| 143 | + |
| 144 | +#### Basic Sample |
| 145 | +```ts |
| 146 | + constructor(private afs: AngularFirestore): { |
| 147 | + const shirtsCollection = afs.collection<Item>('tshirts'); |
| 148 | + shirtsCollection.add({ name: 'item', price: 10 }); |
| 149 | + } |
| 150 | +``` |
| 151 | + |
| 152 | +## Manipulating individual documents |
| 153 | + |
| 154 | +To retrieve, update, or delete an individual document you can use the `doc()` method. This method returns an `AngularFirestoreDocument`, which provides methods for streaming, updating, and deleting. [See Using Documents with AngularFirestore for more information on how to use documents](documents.md). |
| 155 | + |
| 156 | +### [Next Step: Querying Collections in AngularFirestore](querying-collections.md) |
0 commit comments