From 45dcb64e42e6f7fcd10629d5f49661924a9a62c7 Mon Sep 17 00:00:00 2001 From: codediodeio Date: Tue, 18 Dec 2018 19:51:24 -0700 Subject: [PATCH 1/4] feat(firestore): Added option to include document IDs on valueChanges() An 'idField' string arg can be used with collection.valueChanges() to include the document ID on the emitted data payload. --- docs/firestore/collections.md | 4 ++-- src/firestore/collection/collection.spec.ts | 14 +++++++++++++- src/firestore/collection/collection.ts | 10 ++++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/firestore/collections.md b/docs/firestore/collections.md index 70148d0e1..e709589bb 100644 --- a/docs/firestore/collections.md +++ b/docs/firestore/collections.md @@ -73,9 +73,9 @@ 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 method provides only the data. Optionally, you can pass an `idField` string to include the document ID. **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. diff --git a/src/firestore/collection/collection.spec.ts b/src/firestore/collection/collection.spec.ts index 8134b9e35..8352b5d57 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..7c8eb80ce 100644 --- a/src/firestore/collection/collection.ts +++ b/src/firestore/collection/collection.ts @@ -104,12 +104,18 @@ export class AngularFirestoreCollection { /** * Listen to all documents in the collection and its possible query as an Observable. */ - valueChanges(): Observable { + valueChanges(idField?: string): 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 => { + return { + ...a.data() as Object, + ...(idField ? { [idField]: a.id } : null) + } as T; + }) + ) ); } From 04579033e322e3dc4ab3cc8450af75b0a36507d9 Mon Sep 17 00:00:00 2001 From: John Carroll Date: Fri, 25 Jan 2019 08:25:20 -0800 Subject: [PATCH 2/4] feat(firestore): changed valueChanges() arg to options object --- docs/firestore/collections.md | 4 ++-- src/firestore/collection/collection.spec.ts | 2 +- src/firestore/collection/collection.ts | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/firestore/collections.md b/docs/firestore/collections.md index e709589bb..03c2a9526 100644 --- a/docs/firestore/collections.md +++ b/docs/firestore/collections.md @@ -73,9 +73,9 @@ interface DocumentSnapshot { There are multiple ways of streaming collection data from Firestore. -### `valueChanges(idField?: string)` +### `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. Optionally, you can pass an `idField` string to include the document ID. +**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. diff --git a/src/firestore/collection/collection.spec.ts b/src/firestore/collection/collection.spec.ts index 8352b5d57..d0f59138b 100644 --- a/src/firestore/collection/collection.spec.ts +++ b/src/firestore/collection/collection.spec.ts @@ -73,7 +73,7 @@ describe('AngularFirestoreCollection', () => { const ITEMS = 1; const { ref, stocks, names } = await collectionHarness(afs, ITEMS); const idField = 'myCustomID'; - const sub = stocks.valueChanges(idField).subscribe(data => { + const sub = stocks.valueChanges({idField}).subscribe(data => { sub.unsubscribe(); const stock = data[0]; expect(stock[idField]).toBeDefined(); diff --git a/src/firestore/collection/collection.ts b/src/firestore/collection/collection.ts index 7c8eb80ce..dd853bfd7 100644 --- a/src/firestore/collection/collection.ts +++ b/src/firestore/collection/collection.ts @@ -103,8 +103,12 @@ 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(idField?: string): Observable { + valueChanges(options: {idField?: string} = {}): Observable { const fromCollectionRef$ = fromCollectionRef(this.query); const scheduled$ = this.afs.scheduler.runOutsideAngular(fromCollectionRef$); return this.afs.scheduler.keepUnstableUntilFirst(scheduled$) @@ -112,7 +116,7 @@ export class AngularFirestoreCollection { map(actions => actions.payload.docs.map(a => { return { ...a.data() as Object, - ...(idField ? { [idField]: a.id } : null) + ...(options.idField ? { [options.idField]: a.id } : null) } as T; }) ) From 810cff57a75faf3eb4baca3d77790784f899a3a0 Mon Sep 17 00:00:00 2001 From: thefliik Date: Fri, 25 Jan 2019 08:55:05 -0800 Subject: [PATCH 3/4] chore(docs): fix Collection#valueChanges() docs --- docs/firestore/collections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/firestore/collections.md b/docs/firestore/collections.md index 03c2a9526..8099f5988 100644 --- a/docs/firestore/collections.md +++ b/docs/firestore/collections.md @@ -79,7 +79,7 @@ There are multiple ways of streaming collection data from Firestore. **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. From d1dfa42205d837629adae55d653e2eec365bc55f Mon Sep 17 00:00:00 2001 From: James Daniels Date: Fri, 10 May 2019 18:11:58 -0700 Subject: [PATCH 4/4] Adding idField into the return type, if provided --- src/firestore/collection/collection.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/firestore/collection/collection.ts b/src/firestore/collection/collection.ts index dd853bfd7..c1d6f3f0a 100644 --- a/src/firestore/collection/collection.ts +++ b/src/firestore/collection/collection.ts @@ -108,18 +108,24 @@ export class AngularFirestoreCollection { * provided `idField` property name. * @param options */ - valueChanges(options: {idField?: string} = {}): 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 => { + map(actions => actions.payload.docs.map(a => { + if (options.idField) { return { ...a.data() as Object, - ...(options.idField ? { [options.idField]: a.id } : null) - } as T; - }) - ) + ...{ [options.idField]: a.id } + } as T & { [T in K]: string }; + } else { + return a.data() + } + })) ); }