diff --git a/docs/firestore/collections.md b/docs/firestore/collections.md index 70148d0e1..8099f5988 100644 --- a/docs/firestore/collections.md +++ b/docs/firestore/collections.md @@ -73,13 +73,13 @@ interface DocumentSnapshot { There are multiple ways of streaming collection data from Firestore. -### `valueChanges()` +### `valueChanges({idField?: string})` -**What is it?** - The current state of your collection. 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. +**What is it?** - The current state of your collection. Returns an Observable of data as a synchronized array of JSON objects. All Snapshot metadata is stripped and just the document data is included. Optionally, you can pass an options object with an `idField` key containing a string. If provided, the returned JSON objects will include their document ID mapped to a property with the name provided by `idField`. **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. -**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 methods. This method assumes you either are saving the `id` to the document data or using a "readonly" approach. +**When would you not use it?** - When you need a more complex data structure than an array. **Best practices** - Use this method to display data on a page. It's simple but effective. Use `.snapshotChanges()` once your needs become more complex. diff --git a/src/firestore/collection/collection.spec.ts b/src/firestore/collection/collection.spec.ts index e7f9c965b..1f2bf08a7 100644 --- a/src/firestore/collection/collection.spec.ts +++ b/src/firestore/collection/collection.spec.ts @@ -1,7 +1,6 @@ import { FirebaseApp, AngularFireModule } from '@angular/fire'; import { AngularFirestore } from '../firestore'; import { AngularFirestoreModule } from '../firestore.module'; -import { AngularFirestoreDocument } from '../document/document'; import { AngularFirestoreCollection } from './collection'; import { QueryFn } from '../interfaces'; import { Observable, BehaviorSubject, Subscription } from 'rxjs'; @@ -70,6 +69,19 @@ describe('AngularFirestoreCollection', () => { }); + it('should optionally map the doc ID to the emitted data object', async (done: any) => { + const ITEMS = 1; + const { ref, stocks, names } = await collectionHarness(afs, ITEMS); + const idField = 'myCustomID'; + const sub = stocks.valueChanges({idField}).subscribe(data => { + sub.unsubscribe(); + const stock = data[0]; + expect(stock[idField]).toBeDefined(); + expect(stock).toEqual(jasmine.objectContaining(FAKE_STOCK_DATA)); + deleteThemAll(names, ref).then(done).catch(fail); + }) + }); + it('should handle multiple subscriptions (hot)', async (done: any) => { const ITEMS = 4; const { randomCollectionName, ref, stocks, names } = await collectionHarness(afs, ITEMS); diff --git a/src/firestore/collection/collection.ts b/src/firestore/collection/collection.ts index 5bb454096..c1d6f3f0a 100644 --- a/src/firestore/collection/collection.ts +++ b/src/firestore/collection/collection.ts @@ -103,13 +103,29 @@ export class AngularFirestoreCollection { /** * Listen to all documents in the collection and its possible query as an Observable. + * + * If the `idField` option is provided, document IDs are included and mapped to the + * provided `idField` property name. + * @param options */ - valueChanges(): Observable { + valueChanges(): Observable + valueChanges({}): Observable + valueChanges(options: {idField: K}): Observable<(T & { [T in K]: string })[]> + valueChanges(options: {idField?: K} = {}): Observable { const fromCollectionRef$ = fromCollectionRef(this.query); const scheduled$ = this.afs.scheduler.runOutsideAngular(fromCollectionRef$); return this.afs.scheduler.keepUnstableUntilFirst(scheduled$) .pipe( - map(actions => actions.payload.docs.map(a => a.data())) + map(actions => actions.payload.docs.map(a => { + if (options.idField) { + return { + ...a.data() as Object, + ...{ [options.idField]: a.id } + } as T & { [T in K]: string }; + } else { + return a.data() + } + })) ); }