-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
Copy pathcollection.ts
154 lines (144 loc) · 5.89 KB
/
collection.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { from, Observable } from 'rxjs';
import { filter, map, pairwise, scan, startWith } from 'rxjs/operators';
import firebase from 'firebase/compat/app';
import { keepUnstableUntilFirst } from '@angular/fire';
import { CollectionReference, DocumentChangeAction, DocumentChangeType, DocumentData, DocumentReference, Query } from '../interfaces';
import { docChanges, sortedChanges } from './changes';
import { AngularFirestoreDocument } from '../document/document';
import { fromCollectionRef } from '../observable/fromRef';
import { AngularFirestore } from '../firestore';
export function validateEventsArray(events?: DocumentChangeType[]) {
if (!events || events.length === 0) {
events = ['added', 'removed', 'modified'];
}
return events;
}
/**
* AngularFirestoreCollection service
*
* This class creates a reference to a Firestore Collection. A reference and a query are provided in
* in the constructor. The query can be the unqueried reference if no query is desired.The class
* is generic which gives you type safety for data update methods and data streaming.
*
* This class uses Symbol.observable to transform into Observable using Observable.from().
*
* This class is rarely used directly and should be created from the AngularFirestore service.
*
* Example:
*
* const collectionRef = firebase.firestore.collection('stocks');
* const query = collectionRef.where('price', '>', '0.01');
* const fakeStock = new AngularFirestoreCollection<Stock>(collectionRef, query);
*
* // NOTE!: the updates are performed on the reference not the query
* await fakeStock.add({ name: 'FAKE', price: 0.01 });
*
* // Subscribe to changes as snapshots. This provides you data updates as well as delta updates.
* fakeStock.valueChanges().subscribe(value => console.log(value));
*/
export class AngularFirestoreCollection<T = DocumentData> {
/**
* The constructor takes in a CollectionReference and Query to provide wrapper methods
* for data operations and data streaming.
*
* Note: Data operation methods are done on the reference not the query. This means
* when you update data it is not updating data to the window of your query unless
* the data fits the criteria of the query. See the AssociatedRefence type for details
* on this implication.
*/
constructor(
public readonly ref: CollectionReference<T>,
private readonly query: Query<T>,
private readonly afs: AngularFirestore) { }
/**
* Listen to the latest change in the stream. This method returns changes
* as they occur and they are not sorted by query order. This allows you to construct
* your own data structure.
*/
stateChanges(events?: DocumentChangeType[]): Observable<DocumentChangeAction<T>[]> {
let source = docChanges<T>(this.query, this.afs.schedulers.outsideAngular);
if (events && events.length > 0) {
source = source.pipe(
map(actions => actions.filter(change => events.indexOf(change.type) > -1))
);
}
return source.pipe(
// We want to filter out empty arrays, but always emit at first, so the developer knows
// that the collection has been resolve; even if it's empty
startWith<DocumentChangeAction<T>[], undefined>(undefined),
pairwise(),
filter(([prior, current]) => current.length > 0 || !prior),
map(([prior, current]) => current),
keepUnstableUntilFirst
);
}
/**
* Create a stream of changes as they occur it time. This method is similar to stateChanges()
* but it collects each event in an array over time.
*/
auditTrail(events?: DocumentChangeType[]): Observable<DocumentChangeAction<T>[]> {
return this.stateChanges(events).pipe(scan((current, action) => [...current, ...action], []));
}
/**
* Create a stream of synchronized changes. This method keeps the local array in sorted
* query order.
*/
snapshotChanges(events?: DocumentChangeType[]): Observable<DocumentChangeAction<T>[]> {
const validatedEvents = validateEventsArray(events);
const scheduledSortedChanges$ = sortedChanges<T>(this.query, validatedEvents, this.afs.schedulers.outsideAngular);
return scheduledSortedChanges$.pipe(
keepUnstableUntilFirst
);
}
/**
* 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.
*/
valueChanges(): Observable<T[]>;
// tslint:disable-next-line:unified-signatures
valueChanges(): Observable<T[]>;
valueChanges<K extends string>(options: {idField: K}): Observable<(T & { [T in K]: string })[]>;
valueChanges<K extends string>(options: {idField?: K} = {}): Observable<T[]> {
return fromCollectionRef<T>(this.query, this.afs.schedulers.outsideAngular)
.pipe(
map(actions => actions.payload.docs.map(a => {
if (options.idField) {
return {
...a.data() as {},
...{ [options.idField]: a.id }
} as T & { [T in K]: string };
} else {
return a.data();
}
})),
keepUnstableUntilFirst
);
}
/**
* Retrieve the results of the query once.
*/
get(options?: firebase.firestore.GetOptions) {
return from(this.query.get(options)).pipe(
keepUnstableUntilFirst,
);
}
/**
* Add data to a collection reference.
*
* Note: Data operation methods are done on the reference not the query. This means
* when you update data it is not updating data to the window of your query unless
* the data fits the criteria of the query.
*/
add(data: T): Promise<DocumentReference<T>> {
return this.ref.add(data);
}
/**
* Create a reference to a single document in a collection.
*/
doc<T2 = T>(path?: string): AngularFirestoreDocument<T2> {
// TODO is there a better way to solve this type issue
return new AngularFirestoreDocument(this.ref.doc(path) as any, this.afs);
}
}