src/lib/firestore-extended.ts
Main Class.
constructor(fs: BaseFirestoreWrapper, defaultDocId: string)
|
||||||||||||
Defined in src/lib/firestore-extended.ts:79
|
||||||||||||
Constructor for AngularFirestoreWrapper
Parameters :
|
Public defaultDocId |
Type : string
|
Default value : 'data'
|
Defined in src/lib/firestore-extended.ts:87
|
The default name given to a subCollection document when no name is given
|
Public add$ | |||||||||||||||||||||||||
add$(data: T, collectionRef: CollectionReference
|
|||||||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:259
|
|||||||||||||||||||||||||
Type parameters :
|
|||||||||||||||||||||||||
Add document to firestore and split it up into sub collection.
Parameters :
Returns :
Observable<FireItem<T>>
|
Protected addSimple$ | ||||||||||||||||
addSimple$(data: T, collectionRef: CollectionReference
|
||||||||||||||||
Defined in src/lib/firestore-extended.ts:1172
|
||||||||||||||||
Type parameters :
|
||||||||||||||||
A replacement/extension to the AngularFirestoreCollection.add. Does the same as AngularFirestoreCollection.add but can also add createdDate and modifiedDate and returns the data with the added properties in FirebaseDbItem Used internally
Parameters :
Returns :
Observable<FireItem<T>>
|
Protected batchCommit$ | ||||||
batchCommit$(batch: WriteBatch)
|
||||||
Defined in src/lib/firestore-extended.ts:1622
|
||||||
Turn a batch into an Observable instead of Promise. For some reason angularfire returns a promise on batch.commit() instead of an observable like for everything else. This method turns it into an observable
Parameters :
Returns :
Observable<void>
|
Public changeDocId$ | |||||||||||||||||||||||||
changeDocId$(docRef: DocumentReference
|
|||||||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:470
|
|||||||||||||||||||||||||
Type parameters :
|
|||||||||||||||||||||||||
Firestore doesn't allow you do change the name or move a doc directly so you will have to create a new doc under the new name and then delete the old doc. returns the new doc once the delete is done.
Parameters :
Returns :
Observable<FireItem<T>>
|
cleanExtrasFromData | ||||||||||||||||
cleanExtrasFromData(data: | FireItem, subCollectionWriters?: SubCollectionWriter[], additionalFieldsToRemove?: string[])
|
||||||||||||||||
Defined in src/lib/firestore-extended.ts:898
|
||||||||||||||||
Type parameters :
|
||||||||||||||||
clean FirestoreBaseItem properties from the data. Usually done if you wish to save the data to firestore, since some FirestoreBaseItem properties are of non allowed types. Goes through each level and removes DbItemExtras In case you wish to save the data
Parameters :
Returns :
T
|
cleanExtrasFromData | ||||||||||||
cleanExtrasFromData(datas: Array< | FireItem>, subCollectionWriters?: SubCollectionWriter[], additionalFieldsToRemove?: string[])
|
||||||||||||
Defined in src/lib/firestore-extended.ts:902
|
||||||||||||
Type parameters :
|
||||||||||||
Parameters :
Returns :
Array<T>
|
Public cleanExtrasFromData | ||||||||||||||||
cleanExtrasFromData(data: | Array< | FireItem>, subCollectionWriters: SubCollectionWriter[], additionalFieldsToRemove: string[])
|
||||||||||||||||
Defined in src/lib/firestore-extended.ts:906
|
||||||||||||||||
Type parameters :
|
||||||||||||||||
Parameters :
Returns :
T | Array
|
Public delete$ | |||||||||||||||
delete$(docRef: DocumentReference, subCollectionQueries: SubCollectionQuery[])
|
|||||||||||||||
Defined in src/lib/firestore-extended.ts:747
|
|||||||||||||||
Delete Document and child documents. Takes a DocumentReference and an optional list of SubCollectionQuery
Parameters :
Returns :
Observable<void>
|
Public deleteCollection$ | ||||||||||||
deleteCollection$(collectionRef: CollectionReference
|
||||||||||||
Defined in src/lib/firestore-extended.ts:839
|
||||||||||||
Type parameters :
|
||||||||||||
Delete all documents and sub collections as specified in subCollectionQueries. Not very efficient and causes a lot of db reads. If possible use the firebase CLI or the console instead when deleting large collections.
Parameters :
Returns :
Observable<any>
|
Public deleteDocByPath$ | |||||||||||||||
deleteDocByPath$(docPath: string, subCollectionQueries: SubCollectionQuery[])
|
|||||||||||||||
Defined in src/lib/firestore-extended.ts:856
|
|||||||||||||||
Delete firestore document by path Convenience method in case we do not have direct access to the AngularFirestoreDocument reference
Parameters :
Returns :
Observable<any>
|
Public deleteIndexedItemInArray$ | |||||||||||||||||||||||||
deleteIndexedItemInArray$(items: Array<FireItem<T>>, indexToDelete: number, subCollectionQueries: SubCollectionQuery[], useCopy: boolean)
|
|||||||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:595
|
|||||||||||||||||||||||||
Type parameters :
|
|||||||||||||||||||||||||
Use when you wish to delete an indexed document and have the remaining documents update their indices to reflect the change.
Parameters :
Returns :
Observable<void>
|
Public deleteIndexedItemsInArray$ | |||||||||||||||||||||||||
deleteIndexedItemsInArray$(items: Array<FireItem<T>>, indicesToDelete: number[], subCollectionQueries: SubCollectionQuery[], useCopy: boolean)
|
|||||||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:634
|
|||||||||||||||||||||||||
Type parameters :
|
|||||||||||||||||||||||||
Use when you wish to delete several indexed documents and have the remaining documents update their indices to reflect the change.
Parameters :
Returns :
Observable<void>
|
Public deleteItem$ | |||||||||||||||
deleteItem$(item: FireItem<T>, subCollectionQueries: SubCollectionQuery[])
|
|||||||||||||||
Defined in src/lib/firestore-extended.ts:870
|
|||||||||||||||
Type parameters :
|
|||||||||||||||
Delete document by FirestoreItem A very convenient method to remove a previously fetched document. Requires that the document/Item is previously fetched since the item needs to be a FireItem, i.e. includes firestoreMetadata.
Parameters :
Returns :
Observable<any>
|
Public deleteMultiple$ | |||||||||||||||
deleteMultiple$(docRefs: DocumentReference
|
|||||||||||||||
Defined in src/lib/firestore-extended.ts:799
|
|||||||||||||||
Type parameters :
|
|||||||||||||||
Delete Documents and child documents
Parameters :
Returns :
Observable<any>
|
Public deleteMultipleByPaths$ | ||||||
deleteMultipleByPaths$(docPaths: string[])
|
||||||
Defined in src/lib/firestore-extended.ts:786
|
||||||
Parameters :
Returns :
Observable<any>
|
Protected deleteMultipleSimple$ | ||||||||
deleteMultipleSimple$(docRefs: DocumentReference[])
|
||||||||
Defined in src/lib/firestore-extended.ts:1388
|
||||||||
Delete Documents
Parameters :
Returns :
Observable<void>
|
Protected getBatchFromMoveItemInIndexedDocs | |||||||||||||||||||||||||
getBatchFromMoveItemInIndexedDocs(items: Array<FireItem<T>>, fromIndex: number, toIndex: number, useCopy)
|
|||||||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:549
|
|||||||||||||||||||||||||
Type parameters :
|
|||||||||||||||||||||||||
Does the heavy lifting when it comes to updating multiple docs to change their index. Not called directly.
Parameters :
Returns :
WriteBatch
|
Protected getBatchFromTransferItemInIndexedDocs | ||||||||||||||||||||||||||||||||||||
getBatchFromTransferItemInIndexedDocs(previousArray: Array<FireItem<T>>, currentArray: Array<FireItem<T>>, previousIndex: number, currentIndex: number, currentArrayName: string, additionalDataUpdateOnMovedItem?: literal type, isUpdateModifiedDateOnMovedItem, useCopy)
|
||||||||||||||||||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:1308
|
||||||||||||||||||||||||||||||||||||
Type parameters :
|
||||||||||||||||||||||||||||||||||||
Used mainly for drag and drop scenarios where we drag an item from one list to another and the the docs have an index value and a groupName.
Parameters :
Returns :
WriteBatch
|
Protected getBatchFromUpdateIndexFromListOfDocs | ||||||||||||
getBatchFromUpdateIndexFromListOfDocs(items: Array<FireItem<T>>, batch: WriteBatch)
|
||||||||||||
Defined in src/lib/firestore-extended.ts:699
|
||||||||||||
Type parameters :
|
||||||||||||
Run this on collections with a fixed order using an index: number attribute; This will update that index with the index in the collectionData, so it should be sorted by index first. Basically needs to be run after a delete
Parameters :
Returns :
WriteBatch
|
Public getDeleteBatch$ | ||||||||||||||||||||
getDeleteBatch$(docRef: DocumentReference, subCollectionQueries: SubCollectionQuery[], batch: WriteBatch)
|
||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:770
|
||||||||||||||||||||
Returns WriteBatch that is set to delete Document and child documents of given docRef
Parameters :
Returns :
Observable<WriteBatch>
|
Protected getDeleteMultipleSimpleBatch | ||||||||||||
getDeleteMultipleSimpleBatch(docRefs: DocumentReference[], batch: WriteBatch)
|
||||||||||||
Defined in src/lib/firestore-extended.ts:1395
|
||||||||||||
Parameters :
Returns :
WriteBatch
|
Protected getDocumentReferencesDeep$ | |||||||||||||||
getDocumentReferencesDeep$(ref: DocumentReference | CollectionReference, subCollectionQueries: SubCollectionQuery[])
|
|||||||||||||||
Defined in src/lib/firestore-extended.ts:1462
|
|||||||||||||||
Returns an Observable containing a list of DocumentReference found under the given docRef using the SubCollectionQuery[] Mainly used to delete a docFs and its sub docs
Parameters :
Returns :
Observable<DocumentReference[]>
|
Protected getDocumentReferencesFromCollectionRef$ | ||||||||||||
getDocumentReferencesFromCollectionRef$(collectionRef: CollectionReference
|
||||||||||||
Defined in src/lib/firestore-extended.ts:1489
|
||||||||||||
Type parameters :
|
||||||||||||
Parameters :
Returns :
Observable<DocumentReference[]>
|
Protected getDocumentReferencesFromDocRef$ | ||||||||||||
getDocumentReferencesFromDocRef$(docRef: DocumentReference
|
||||||||||||
Defined in src/lib/firestore-extended.ts:1473
|
||||||||||||
Type parameters :
|
||||||||||||
Parameters :
Returns :
Observable<DocumentReference[]>
|
Protected getDocumentReferencesFromItem | |||||||||||||||
getDocumentReferencesFromItem(item: FireItem<T>, subCollectionQueries: SubCollectionQuery[])
|
|||||||||||||||
Defined in src/lib/firestore-extended.ts:1525
|
|||||||||||||||
Type parameters :
|
|||||||||||||||
Used by deleteDeepByItem$ to get all the AngularFirestoreDocuments to be deleted including child documents using SubCollectionQueries Internal use
Parameters :
Returns :
DocumentReference[]
|
Protected getPathsFromItemDeepRecursiveHelper | ||||||||||||
getPathsFromItemDeepRecursiveHelper(item: T, subCollectionQueries: SubCollectionQuery[])
|
||||||||||||
Defined in src/lib/firestore-extended.ts:1536
|
||||||||||||
Type parameters :
|
||||||||||||
DO NOT CALL THIS METHOD, its meant as a support method for getDocs$
Parameters :
Returns :
string[]
|
Public listenForCollection$ | |||||||||||||||
listenForCollection$(_query: Query
|
|||||||||||||||
Defined in src/lib/firestore-extended.ts:167
|
|||||||||||||||
Type parameters :
|
|||||||||||||||
Same as AngularFirestoreCollection.snapshotChanges but it adds the properties in FirebaseDbItem. Important to understand this is will trigger for every change/update on any of the documents we are listening to. That means that if any document we are listening to is changed the entire object will be triggered containing the updated data. Example usage. ngFirestoreDeep: RxFirestoreExtended; // RxFirestoreExtended variable restaurantCollectionFs = this.ngFireStore.collection('restaurants'); // AngularFirestoreCollectionRef to restaurants constructor(private ngFireStore: AngularFirestore) { this.ngFirestoreDeep = new RxFirestoreExtended(ngFireStore); // initialize AngularFireStoreDeep with AngularFirestore } listenForRestaurants$(): Observable<RestaurantItem[]> {
return this.ngFirestoreDeep.listenForCollection$ If you do not wish to listen for changes and only care about getting the values once getRestaurants$(): Observable<RestaurantItem[]> {
return this.ngFirestoreDeep.listenForCollection$
Parameters :
Returns :
Observable<Array<FireItem<T>>>
|
Public listenForCollectionRecursively$ | ||||||||||||
listenForCollectionRecursively$(collectionPath: string, collectionKey: string, orderKey?: string)
|
||||||||||||
Defined in src/lib/firestore-extended.ts:202
|
||||||||||||
Type parameters :
|
||||||||||||
Listens for collections inside collections with the same name to an unlimited depth and returns all of it as an array.
Parameters :
Returns :
Observable<any>
|
Protected listenForCollectionsDeep | ||||||||||||
listenForCollectionsDeep(item: FireItem<T>, subCollectionQueries: SubCollectionQuery[])
|
||||||||||||
Defined in src/lib/firestore-extended.ts:1039
|
||||||||||||
Type parameters :
|
||||||||||||
Used internally for both listenForDoc and listenForCollection in order to recursively get collections. Please use listenForDoc or listenForCollection.
Parameters :
Returns :
Observable<FireItem[]>
|
Protected listenForCollectionSimple$ | ||||||||
listenForCollectionSimple$(_query: Query
|
||||||||
Defined in src/lib/firestore-extended.ts:999
|
||||||||
Type parameters :
|
||||||||
Listens for single collection and returns an array of documents as FireItem
Parameters :
Returns :
Observable<Array<FireItem<T>>>
|
Public listenForDoc$ | ||||||||||||||||||||
listenForDoc$(docRef: DocumentReference
|
||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:125
|
||||||||||||||||||||
Type parameters :
|
||||||||||||||||||||
Allows for listening to documents and collections n deep up to the firestore max of 100 levels. Triggers for any change in any document that is listened to. E.x: const subCollectionQueries: SubCollectionQuery[] = [ { name: 'data' }, { name: 'secure' }, { name: 'variants' }, { name: 'images', queryFn: ref => ref.orderBy('index'), collectionWithNames: [ { name: 'secure'} ] }, ];
Wrapper for listenForDocDeepRecursiveHelper$ so that we can cast the return to the correct type All logic is in listenForDocDeepRecursiveHelper$.
Parameters :
Returns :
Observable<FireItem<T>>
|
Protected listenForDocDeepRecursiveHelper$ | ||||||||||||||||
listenForDocDeepRecursiveHelper$(docRef: DocumentReference
|
||||||||||||||||
Defined in src/lib/firestore-extended.ts:1138
|
||||||||||||||||
Type parameters :
|
||||||||||||||||
DO NOT CALL THIS METHOD, meant to be used solely by listenForDocAndSubCollections$
Parameters :
Returns :
Observable<any>
|
Protected listenForDocSimple$ | |||||||||||||||
listenForDocSimple$(docRef: DocumentReference
|
|||||||||||||||
Defined in src/lib/firestore-extended.ts:942
|
|||||||||||||||
Type parameters :
|
|||||||||||||||
Same as AngularFirestoreDocument.snapshotChanges but it adds the properties in FirebaseDbItem and also allows for to choose action to take when document does not exist Important to understand this is will trigger for every change/update on the document we are listening to.
Parameters :
Returns :
Observable<FireItem<T>>
|
Public moveItemInArray$ | |||||||||||||||||||||||||
moveItemInArray$(items: Array<FireItem<T>>, fromIndex: number, toIndex: number, useCopy)
|
|||||||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:516
|
|||||||||||||||||||||||||
Type parameters :
|
|||||||||||||||||||||||||
Moved item within the same list so we need to update the index of all items in the list; Use a copy if you dont wish to update the given array, for example when you want to just listen for the change of the db.. The reason to not do this is because it takes some time for the db to update and it looks better if the list updates immediately.
Parameters :
Returns :
Observable<void>
|
Protected removeDataExtrasRecursiveHelper | ||||||||||||||||||||
removeDataExtrasRecursiveHelper(dbItem: T, subCollectionWriters: SubCollectionWriter[], additionalFieldsToRemove: string[])
|
||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:1411
|
||||||||||||||||||||
Type parameters :
|
||||||||||||||||||||
Recursive method to clean FirestoreBaseItem properties from the dbItem
Parameters :
Returns :
T
|
Protected splitDataIntoCurrentDocAndSubCollections | ||||||||||||
splitDataIntoCurrentDocAndSubCollections(data: T, subCollectionWriters: SubCollectionWriter[])
|
||||||||||||
Defined in src/lib/firestore-extended.ts:1581
|
||||||||||||
Type parameters :
|
||||||||||||
DO NOT CALL THIS METHOD, used in addDeep and updateDeep to split the data into currentDoc and subCollections Only goes one sub level deep;
Parameters :
Returns :
CurrentDocSubCollectionSplit
|
Public transferItemInIndexedDocs | ||||||||||||||||||||||||||||||||||||
transferItemInIndexedDocs(previousArray: Array<FireItem<T>>, currentArray: Array<FireItem<T>>, previousIndex: number, currentIndex: number, currentArrayName: string, additionalDataUpdateOnMovedItem?: literal type, isUpdateModifiedDateOnMovedItem, useCopy)
|
||||||||||||||||||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:715
|
||||||||||||||||||||||||||||||||||||
Type parameters :
|
||||||||||||||||||||||||||||||||||||
Parameters :
Returns :
Observable<void>
|
Public update$ | |||||||||||||||||||||||||
update$(data: UpdateData
|
|||||||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:424
|
|||||||||||||||||||||||||
Type parameters :
|
|||||||||||||||||||||||||
Update document and child documents Be careful when updating a document of any kind since we allow partial data there cannot be any type checking prior to update so its possible to introduce spelling mistakes on attributes and so forth
Parameters :
Returns :
Observable<void>
|
Protected updateDeepToBatchHelper | ||||||||||||||||||||||||
updateDeepToBatchHelper(data: UpdateData
|
||||||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:1251
|
||||||||||||||||||||||||
Type parameters :
|
||||||||||||||||||||||||
DO NOT CALL THIS METHOD, used by update deep
Parameters :
Returns :
WriteBatch
|
Public updateMultiple$ | ||||||||||||||||||||
updateMultiple$(docRefs: DocumentReference[], data: A, isAddModifiedDate: boolean)
|
||||||||||||||||||||
Defined in src/lib/firestore-extended.ts:444
|
||||||||||||||||||||
Type parameters :
|
||||||||||||||||||||
Update/ add data to the firestore documents
Parameters :
Returns :
Observable<void>
|
Protected updateSimple$ | ||||||||||||||||
updateSimple$(data: UpdateData
|
||||||||||||||||
Defined in src/lib/firestore-extended.ts:1238
|
||||||||||||||||
Type parameters :
|
||||||||||||||||
Used internally for updates that doesn't affect child documents
Parameters :
Returns :
Observable<void>
|
firestore |
getfirestore()
|
Defined in src/lib/firestore-extended.ts:90
|
import {combineLatest, forkJoin, from, Observable, of} from 'rxjs';
import {catchError, filter, map, mergeMap, switchMap, take, tap} from 'rxjs/operators';
import {
collection,
CollectionReference,
DocumentData,
DocumentReference,
DocumentSnapshot,
endAt,
endBefore,
FieldPath,
Firestore,
getDocs,
limit,
limitToLast,
orderBy,
OrderByDirection,
query,
Query,
QueryConstraint,
QuerySnapshot,
startAfter,
startAt,
UpdateData,
where,
WhereFilterOp,
writeBatch,
WriteBatch
} from 'firebase/firestore';
import {
addCreatedDate,
addDataToItem,
addModifiedDate,
convertTimestampToDate,
getDocRefWithId,
getRefFromPath,
getSubCollection
} from './helpers';
import {SubCollectionQuery} from './sub-collection-query';
import {BaseFirestoreWrapper, FirestoreErrorExt} from './interfaces';
import {FireItem, FirestoreMetadata, ItemWithIndex, ItemWithIndexGroup} from './models/fireItem';
import {SubCollectionWriter} from './sub-collection-writer';
import {moveItemInArray, transferArrayItem} from './drag-utils';
/**
* Action to be taken by listener if the document does not exist.
*/
export enum DocNotExistAction {
/** returns a null object */
RETURN_NULL,
/** return all the extras such as ref, path and so on but no data, kinda just ignores that the doc isn't there */
RETURN_ALL_BUT_DATA,
/** do not return at all until it does exist */
FILTER,
/** return doc not found error 'doc_not_found' */
THROW_DOC_NOT_FOUND,
}
/** Used internally */
interface CurrentDocSubCollectionSplit {
/** contains the document that is considered the current */
currentDoc: FireItem;
/** sub collections of current document */
subCollections: { [index: string]: any };
}
/**
* Main Class.
*
*
*
*/
export class FirestoreExtended {
/**
* Constructor for AngularFirestoreWrapper
*
* @param fs Firestore wrapper Firestore extended can be used by many Firestore implementations
* @param defaultDocId The default name given to a subCollection document when no name is given
*/
constructor(private fs: BaseFirestoreWrapper, public defaultDocId: string = 'data') {
}
get firestore(): Firestore {
return this.fs.firestore;
}
/* ---------- LISTEN -------------- */
/**
*
* Allows for listening to documents and collections n deep up to the firestore max of 100 levels.
*
* Triggers for any change in any document that is listened to.
*
*
* E.x:
* const subCollectionQueries: SubCollectionQuery[] = [
* { name: 'data' },
* { name: 'secure' },
* { name: 'variants' },
* { name: 'images',
* queryFn: ref => ref.orderBy('index'),
* collectionWithNames: [
* { name: 'secure'}
* ]
* },
* ];
*
* this.listenForDocAndSubCollections<Product>(docFs, collections)
*
* Wrapper for listenForDocDeepRecursiveHelper$ so that we can cast the return to the correct type
* All logic is in listenForDocDeepRecursiveHelper$.
*
* @param docRef - a docRef with potential queryFn
* @param subCollectionQueries - see example
* @param actionIfNotExist Action to take if document does not exist
*/
public listenForDoc$<T extends DocumentData>(
docRef: DocumentReference<T>,
subCollectionQueries: SubCollectionQuery[] = [],
actionIfNotExist: DocNotExistAction = DocNotExistAction.RETURN_ALL_BUT_DATA): Observable<FireItem<T>> {
return this.listenForDocDeepRecursiveHelper$(docRef, subCollectionQueries, actionIfNotExist).pipe(
map(data => data as FireItem<T>)
);
}
/**
* Same as AngularFirestoreCollection.snapshotChanges but it adds the properties in FirebaseDbItem.
*
* Important to understand this is will trigger for every change/update on any of the documents we are listening to.
* That means that if any document we are listening to is changed the entire object will be triggered containing the updated data.
*
*
* Example usage.
*
* ngFirestoreDeep: RxFirestoreExtended; // RxFirestoreExtended variable
* restaurantCollectionFs = this.ngFireStore.collection('restaurants'); // AngularFirestoreCollectionRef to restaurants
*
* constructor(private ngFireStore: AngularFirestore) {
* this.ngFirestoreDeep = new RxFirestoreExtended(ngFireStore); // initialize AngularFireStoreDeep with AngularFirestore
* }
*
* listenForRestaurants$(): Observable<RestaurantItem[]> {
* return this.ngFirestoreDeep.listenForCollection$<RestaurantItem>(this.restaurantCollectionFs);
* }
*
* If you do not wish to listen for changes and only care about getting the values once
*
* getRestaurants$(): Observable<RestaurantItem[]> {
* return this.ngFirestoreDeep.listenForCollection$<RestaurantItem>(this.restaurantCollectionFs).pipe(
* take(1)
* );
* }
*
* @param _query the collectionRef which will be listened to
* @param subCollectionQueries
* @param documentChangeTypes list of DocumentChangeType that will be listened to, if null listen to all
*/
public listenForCollection$<T extends DocumentData>(
_query: Query<T>,
subCollectionQueries: SubCollectionQuery[] = []): Observable<Array<FireItem<T>>> {
/**
* Returns an observable that will emit whenever the ref changes in any way.
* Also adds the id and ref to the object.
*/
return this.listenForCollectionSimple$<T>(_query).pipe(
mergeMap((items: FireItem<T>[]) => {
if (items == null || items.length === 0) {
return of([]);
}
if (subCollectionQueries.length <= 0) {
return of(items);
}
const collectionListeners: Array<Observable<any>> = [];
items.forEach((item: FireItem<T>) => {
const collectionListener = this.listenForCollectionsDeep<T>(item, subCollectionQueries);
collectionListeners.push(collectionListener);
});
/* Finally return the combined collection listeners */
return combineLatest(collectionListeners);
})
);
}
/**
* Listens for collections inside collections with the same name to an unlimited depth and returns all of it as an array.
*/
public listenForCollectionRecursively$<T extends DocumentData>(
collectionPath: string,
collectionKey: string,
orderKey?: string): Observable<any> {
// const collectionRef = getRefFromPath(collectionPath, this.fs.firestore) as CollectionReference<T>;
const collectionQuery = new QueryContainer<T>(getRefFromPath(collectionPath, this.fs.firestore) as CollectionReference<T>);
if (orderKey != null) {
collectionQuery.orderBy(orderKey);
}
return this.listenForCollectionSimple$<T>(collectionQuery.query).pipe(
mergeMap((items: FireItem<T>[]) => {
if (items.length <= 0) {
return of([]);
} // TODO perhaps make this throw an error so that we can skip it
// if (items.length <= 0) { throwError('No more '); }
const nextLevelObs: Array<Observable<FireItem<T>>> = [];
for (const item of items) {
// const nextLevelPath = item.firestoreMetadata.ref.collection(collectionKey).path; // one level deeper
const nextLevelPath = item.firestoreMetadata.ref.path.concat('/', collectionKey); // one level deeper
const nextLevelItems$ = this.listenForCollectionRecursively$(nextLevelPath, collectionKey, orderKey).pipe(
map((nextLevelItems: Array<FireItem<T>>) => {
if (nextLevelItems.length > 0) {
return {...item, [collectionKey]: nextLevelItems} as FireItem<T>;
} else {
return {...item} as FireItem<T>;
} // dont include an empty array
}),
);
nextLevelObs.push(nextLevelItems$);
}
return combineLatest(nextLevelObs).pipe(
tap(val => console.log(val))
);
}),
);
}
/* ---------- ADD -------------- */
/**
* Add document to firestore and split it up into sub collection.
*
* @param data the data to be saved
* @param collectionRef CollectionReference reference to where on firestore the item should be saved
* @param subCollectionWriters see documentation for SubCollectionWriter for more details on how these are used
* @param isAddDates if true 'createdDate' and 'modifiedDate' is added to the data
* @param docId If a docId is given it will use that specific id when saving the doc, if no docId is given a random id will be used.
*/
public add$<T extends DocumentData>(
data: T,
collectionRef: CollectionReference<T>,
subCollectionWriters: SubCollectionWriter[] = [],
// isAddDates: boolean = true,
docId?: string): Observable<FireItem<T>> {
if (Array.isArray(data) && docId && subCollectionWriters.length > 0) {
const error: FirestoreErrorExt = {
name: 'firestoreExt/invalid-sub-collection-writers',
code: 'unknown',
message: 'Cannot have both docId and subCollectionWriters at the same time when data is an array',
stack: '',
data,
subCollectionWriters,
docId
};
throw error;
}
let currentDoc;
let subCollections: { [index: string]: any; } = {};
/* if the data is an array and a docId is given the entire array will be saved in a single document with that docId,
* Each item in the array will be saved as a map with the key being the array index
* We still want the return value of this function to be as an array non as a map
*/
if (Array.isArray(data) && docId) {
currentDoc = data;
} else {
const split = this.splitDataIntoCurrentDocAndSubCollections(data, subCollectionWriters);
currentDoc = split.currentDoc;
subCollections = split.subCollections;
}
return this.addSimple$<T>(currentDoc as T, collectionRef, docId).pipe(
/* Add Sub/sub collections*/
mergeMap((currentData) => {
const subWriters: Array<Observable<any>> = [];
for (const [subCollectionKey, subCollectionValue] of Object.entries(subCollections)) {
let subSubCollectionWriters: SubCollectionWriter[] | undefined; // undefined if no subCollectionWriters
let subDocId: string | undefined;
if (subCollectionWriters) {
subSubCollectionWriters = subCollectionWriters.find(subColl => subColl.name === subCollectionKey)?.subCollections;
subDocId = subCollectionWriters.find(subColl => subColl.name === subCollectionKey)?.docId;
}
const subCollectionRef: CollectionReference = getSubCollection(currentData.firestoreMetadata.ref, subCollectionKey);
/* Handle array and object differently
* For example if array and no docId is given it means we should save each entry as a separate doc.
* If a docId is given we should save it using that docId under a single doc.
* If not an array it will always be saved as a single doc, using this.defaultDocId as the default docId if none is given */
if (Array.isArray(subCollectionValue)) {
if (subDocId !== undefined) { /* not undefined so save it as a single doc under that docId */
/* the pipe only matters for the return subCollectionValue not for writing the data */
const subWriter = this.add$(subCollectionValue, subCollectionRef, subSubCollectionWriters, subDocId).pipe(
map(item => {
// return {[key]: item};
return {key: subCollectionKey, value: item}; /* key and subCollectionValue as separate k,v properties */
})
);
subWriters.push(subWriter);
} else { /* docId is undefined so we save each object in the array separate */
subCollectionValue.forEach((arrayValue: T) => {
/* the pipe only matters for the return subCollectionValue not for writing the data */
const subWriter = this.add$(arrayValue, subCollectionRef, subSubCollectionWriters).pipe(
map((item) => {
// return {[key]: [item]};
/* key and subCollectionValue as separate k,v properties -- subCollectionValue in an array */
return {key: subCollectionKey, value: [item]};
})
);
subWriters.push(subWriter);
});
}
} else { /* Not an array so a single Object*/
subDocId = subDocId !== undefined ? subDocId : this.defaultDocId;
/* the pipe only matters for the return subCollectionValue not for writing the data */
const subWriter = this.add$(subCollectionValue, subCollectionRef, subSubCollectionWriters, subDocId).pipe(
map(item => {
// return {[key]: item};
return {key: subCollectionKey, value: item}; /* key and subCollectionValue as separate k,v properties */
})
);
subWriters.push(subWriter);
}
} /* end of iteration */
if (subWriters.length > 0) { /* if subWriters.length > 0 it means we need to handle the subWriters */
/* the pipe only matters for the return value not for writing the data */
return combineLatest(subWriters).pipe(
// tap(sub => console.log(sub)),
// TODO super duper ugly way of joining the data together but I cannot think of a better way..also it doesnt really matter.
// TODO The ugliness only relates to how the return object looks after we add, it has no effect on how the object is saved on
// TODO firestore.
map((docDatas: Array<Map<string, []>>) => { /* List of sub docs*/
const groupedData = {};
docDatas.forEach((doc: { [indexKey: string]: any }) => { /* iterate over each doc */
// tslint:disable-next-line:no-string-literal
const key = doc['key'];
// tslint:disable-next-line:no-string-literal
const value = doc['value'];
/* if groupedData has the key already it means that the several docs have the same key..so an array */
// @ts-ignore
if (groupedData.hasOwnProperty(key) && Array.isArray(groupedData[key])) {
/* groupedData[key] must be an array since it already exist..add this doc.value to the array */
// @ts-ignore
(groupedData[key] as Array<any>).push(value[0]);
} else {
// @ts-ignore
groupedData[key] = value;
}
});
return groupedData as T;
}),
// tap(groupedData => console.log(groupedData)),
map((groupedData: T) => {
return {...currentData, ...groupedData} as T;
}),
// tap(d => console.log(d)),
);
} else {
return of(currentData);
}
})
).pipe(
// @ts-ignore
take(1)
);
}
/* ---------- EDIT -------------- */
/**
* Update document and child documents
*
* Be careful when updating a document of any kind since we allow partial data there cannot be any type checking prior to update
* so its possible to introduce spelling mistakes on attributes and so forth
*
* @param data the data that is to be added or updated { [field: string]: any }
* @param docRef DocumentReference to be updated
* @param subCollectionWriters if the data contains properties that should be placed in child collections and documents specify that here
* @param isAddModifiedDate if true the modifiedDate property is added/updated on the affected documents
*/
public update$<T extends DocumentData>(data: UpdateData<Partial<T>>,
docRef: DocumentReference<T>,
subCollectionWriters: SubCollectionWriter[] = [],
isAddModifiedDate: boolean = true): Observable<void> {
if (subCollectionWriters == null || subCollectionWriters.length === 0) {
return this.updateSimple$(data, docRef, isAddModifiedDate); // no subCollectionWriters so just do a simple update
}
const batch = this.updateDeepToBatchHelper(data, docRef, subCollectionWriters, isAddModifiedDate);
return this.batchCommit$(batch);
}
/**
* Update/ add data to the firestore documents
*
* @param docRefs list of DocumentReference to be have their data updated
* @param data data to add/update
* @param isAddModifiedDate if true the modifiedDate is added/updated
*/
public updateMultiple$<A>(docRefs: DocumentReference[], data: A, isAddModifiedDate: boolean = true): Observable<void> {
// const batch = this.fs.firebaseApp.firestore().batch();
const batch: WriteBatch = writeBatch(this.fs.firestore);
if (isAddModifiedDate) {
data = addModifiedDate(data, false);
}
docRefs.forEach((docRef) => {
batch.update(docRef, data);
});
return this.batchCommit$(batch);
}
/**
* Firestore doesn't allow you do change the name or move a doc directly so you will have to create a new doc under the new name
* and then delete the old doc.
* returns the new doc once the delete is done.
*
* @param docRef DocumentReference to have its id changed
* @param newId the new id
* @param subCollectionQueries if the document has child documents the subCollectionQueries are needed to locate them
* @param subCollectionWriters if the document has child documents the SubCollectionWriters are needed to add them back
*/
public changeDocId$<T extends DocumentData>(docRef: DocumentReference<T>,
newId: string,
subCollectionQueries: SubCollectionQuery[] = [],
subCollectionWriters?: SubCollectionWriter[]): Observable<FireItem<T>> {
if (subCollectionWriters == null) {
subCollectionWriters = subCollectionQueries as SubCollectionWriter[];
}
const collectionRef: CollectionReference<T> = docRef.parent;
return this.listenForDoc$<T>(docRef, subCollectionQueries).pipe(
// @ts-ignore
take(1),
map((oldData: T) => this.cleanExtrasFromData(oldData, subCollectionWriters)),
switchMap((oldData: T) => {
return this.add$<T>(oldData, collectionRef, subCollectionWriters, newId).pipe( /* add the data under id*/
mergeMap(newData => { /* delete the old doc */
return this.delete$(docRef, subCollectionQueries).pipe(
map(() => newData) /* keep the new data */
);
}),
);
}),
catchError(err => {
console.log('Failed to Change Doc Id: ' + err);
throw err;
}),
take(1),
);
}
/* Move Item in Array */
/**
* Moved item within the same list so we need to update the index of all items in the list;
* Use a copy if you dont wish to update the given array, for example when you want to just listen for the change of the db..
* The reason to not do this is because it takes some time for the db to update and it looks better if the list updates immediately.
*
* @param items array of FireItem<A> docs with index variables to be updated
* @param fromIndex
* @param toIndex
* @param useCopy if true the given array will not be updated
*/
public moveItemInArray$<T extends DocumentData & ItemWithIndex>(items: Array<FireItem<T>>,
fromIndex: number,
toIndex: number,
useCopy = false): Observable<void> {
if (fromIndex == null || toIndex == null || fromIndex === toIndex || items.length <= 0) { // we didnt really move anything
return of();
}
if (items[0]?.firestoreMetadata == null) {
const error: FirestoreErrorExt = {
name: 'firestoreExt/unable-to-change-index-of-non-document',
code: 'not-found',
message: 'The array does not appear to be a firestore document or FireItem since it lacks firestoreMetadata',
};
throw error;
}
const batch = this.getBatchFromMoveItemInIndexedDocs(items, fromIndex, toIndex, useCopy);
return this.batchCommit$(batch);
}
/**
* Does the heavy lifting when it comes to updating multiple docs to change their index.
* Not called directly.
*
* @param items array of FireItem<A> docs with index variables to be updated
* @param fromIndex
* @param toIndex
* @param useCopy if true the given array will not be updated
* @protected
*/
protected getBatchFromMoveItemInIndexedDocs<T extends DocumentData & ItemWithIndex>(items: Array<FireItem<T>>,
fromIndex: number,
toIndex: number,
useCopy = false): WriteBatch {
const lowestIndex = Math.min(fromIndex, toIndex);
const batch: WriteBatch = writeBatch(this.fs.firestore);
if (fromIndex == null || toIndex == null || fromIndex === toIndex) { // we didnt really move anything
return batch;
}
let usedItems: Array<FireItem<T>>;
if (useCopy) {
usedItems = Object.assign([], items);
} else {
usedItems = items;
}
moveItemInArray<FireItem<T>>(usedItems, fromIndex, toIndex);
const listSliceToUpdate: Array<FireItem<T>> = usedItems.slice(lowestIndex);
let i = lowestIndex;
for (const item of listSliceToUpdate) {
if (!useCopy) { // this is just so that the given array's index is also updated immediately
item.index = i;
}
const ref = getRefFromPath(item.firestoreMetadata.path, this.fs.firestore) as DocumentReference;
batch.update(ref, {index: i});
i++;
}
return batch;
}
/**
* Use when you wish to delete an indexed document and have the remaining documents update their indices to reflect the change.
*
* @param items array of FireItem<A> docs with index variables to be updated
* @param indexToDelete
* @param subCollectionQueries
* @param useCopy
*/
public deleteIndexedItemInArray$<T extends DocumentData & ItemWithIndex>(items: Array<FireItem<T>>,
indexToDelete: number,
subCollectionQueries: SubCollectionQuery[] = [],
useCopy: boolean = false): Observable<void> {
let usedItems: Array<FireItem<T>>;
if (useCopy) {
usedItems = Object.assign([], items);
} else {
usedItems = items;
}
const itemToDelete = usedItems[indexToDelete];
// get the delete batch that also contains any sub collections of the item
return this.getDeleteBatch$(itemToDelete.firestoreMetadata.ref, subCollectionQueries).pipe(
map((batch) => {
// sort and remove the item from the usedItems and then add the update index to the batch
usedItems.sort(item => item.index); // make sure array is sorted by index
usedItems.splice(indexToDelete, 1);
this.getBatchFromUpdateIndexFromListOfDocs<T>(usedItems, batch);
return batch;
}),
switchMap((batch) => this.batchCommit$(batch))
);
}
/**
* Use when you wish to delete several indexed documents and have the remaining documents update their indices to reflect the change.
*
* @param items array of FireItem<A> docs with index variables to be updated
* @param indicesToDelete
* @param subCollectionQueries
* @param useCopy
*/
public deleteIndexedItemsInArray$<T extends DocumentData & ItemWithIndex>(items: Array<FireItem<T>>,
indicesToDelete: number[],
subCollectionQueries: SubCollectionQuery[] = [],
useCopy: boolean = false): Observable<void> {
let usedItems: Array<FireItem<T>>;
if (useCopy) {
usedItems = Object.assign([], items);
} else {
usedItems = items;
}
usedItems.sort(item => item.index); // make sure array is sorted by index
const itemsToDelete = usedItems.filter((item, i) => {
return indicesToDelete.findIndex(_i => _i === i) !== -1;
});
// iterate in reverse so as to not change the indices,
// the indices to delete must also be sorted
indicesToDelete.sort();
for (let i = indicesToDelete.length - 1; i >= 0; i--) {
usedItems.splice(indicesToDelete[i], 1);
}
const docRefsObs$: Observable<DocumentReference[]>[] = [];
// get the docRefs for items to be deleted including the ones in the subCollections
itemsToDelete.forEach(itemToDelete => {
const obs$ = this.getDocumentReferencesDeep$(itemToDelete.firestoreMetadata.ref, subCollectionQueries).pipe(
take(1)
);
docRefsObs$.push(obs$);
});
return forkJoin(docRefsObs$).pipe(
take(1),
map((listOfDocRefs) => {
// concat all the separate docRefs lists into one array of docRefs
let docRefs: DocumentReference[] = [];
listOfDocRefs.forEach(refs => {
docRefs = docRefs.concat(refs);
});
return docRefs;
}),
map((docRefs: DocumentReference<DocumentData>[]) => this.getDeleteMultipleSimpleBatch(docRefs)),
map((batch: WriteBatch) => this.getBatchFromUpdateIndexFromListOfDocs<T>(usedItems, batch)),
switchMap((batch) => this.batchCommit$(batch))
);
}
/**
* Run this on collections with a fixed order using an index: number attribute;
* This will update that index with the index in the collectionData, so it should be sorted by index first.
* Basically needs to be run after a delete
*
* @param items
* @param batch
* @protected
*/
protected getBatchFromUpdateIndexFromListOfDocs<T extends DocumentData & ItemWithIndex>(
items: Array<FireItem<T>>,
batch: WriteBatch = writeBatch(this.fs.firestore)
): WriteBatch {
items.forEach((item, index) => {
if (item.index !== index) {
item.index = index; // this is just so that the given array's index is also updated immediately
const ref = getRefFromPath(item.firestoreMetadata.path, this.fs.firestore) as DocumentReference;
batch.update(ref, {index});
}
});
return batch;
}
public transferItemInIndexedDocs<T extends DocumentData & ItemWithIndexGroup>(
previousArray: Array<FireItem<T>>,
currentArray: Array<FireItem<T>>,
previousIndex: number,
currentIndex: number,
currentArrayName: string,
additionalDataUpdateOnMovedItem?: { [key: string]: any },
isUpdateModifiedDateOnMovedItem = true,
useCopy = false): Observable<void> {
const batch: WriteBatch = this.getBatchFromTransferItemInIndexedDocs(previousArray,
currentArray,
previousIndex,
currentIndex,
currentArrayName,
additionalDataUpdateOnMovedItem,
isUpdateModifiedDateOnMovedItem,
useCopy);
return this.batchCommit$(batch);
}
/* ---------- DELETE -------------- */
/**
* Delete Document and child documents.
* Takes a DocumentReference and an optional list of SubCollectionQuery
*
* @param docRef DocumentReference that is to be deleted
* @param subCollectionQueries if the document has child documents the subCollectionQueries are needed to locate them
*/
public delete$(docRef: DocumentReference, subCollectionQueries: SubCollectionQuery[] = []): Observable<void> {
if (subCollectionQueries == null || subCollectionQueries.length === 0) {
// not deep so just do a normal doc delete
return this.fs.delete(docRef);
}
return this.getDocumentReferencesDeep$(docRef, subCollectionQueries).pipe(
switchMap((docRefList: DocumentReference<DocumentData>[]) => this.deleteMultipleSimple$(docRefList)),
// catchError((err) => { // TODO super ugly and I dont know why this error is thrown..still works
// if (err === 'Document Does not exists') { return of(); }
// else { throw err; }
// }),
);
}
/**
* Returns WriteBatch that is set to delete Document and child documents of given docRef
*
* @param docRef DocumentReference that is to be deleted
* @param subCollectionQueries if the document has child documents the subCollectionQueries are needed to locate them
* @param batch
*/
public getDeleteBatch$(docRef: DocumentReference,
subCollectionQueries: SubCollectionQuery[] = [],
batch: WriteBatch = writeBatch(this.fs.firestore)): Observable<WriteBatch> {
if (subCollectionQueries == null || subCollectionQueries.length === 0) {
// not deep so just do a normal doc delete
batch.delete(docRef);
return of(batch);
}
return this.getDocumentReferencesDeep$(docRef, subCollectionQueries).pipe(
map((docRefs: DocumentReference<DocumentData>[]) => this.getDeleteMultipleSimpleBatch(docRefs)),
take(1)
);
}
public deleteMultipleByPaths$(docPaths: string[]): Observable<any> {
const docRefs: DocumentReference[] =
docPaths.map(path => getRefFromPath(path, this.fs.firestore) as DocumentReference);
return this.deleteMultipleSimple$(docRefs);
}
/**
* Delete Documents and child documents
*
* @param docRefs - A list of DocumentReference that are to be deleted
* @param subCollectionQueries if the document has child documents the subCollectionQueries are needed to locate them
*/
public deleteMultiple$<T = FireItem>(docRefs: DocumentReference<T>[],
subCollectionQueries: SubCollectionQuery[] = []): Observable<any> {
if (subCollectionQueries == null || subCollectionQueries.length === 0) {
return this.deleteMultipleSimple$(docRefs);
}
const deepDocRefs$: Array<Observable<any>> = [];
docRefs.forEach(docRef => {
const docRefs$ = this.getDocumentReferencesDeep$(docRef, subCollectionQueries);
deepDocRefs$.push(docRefs$);
});
return combineLatest(deepDocRefs$).pipe(
// tap(lists => console.log(lists)),
map((lists: any[]) => {
let mainDocRefList: DocumentReference[] = [];
lists.forEach(list => {
mainDocRefList = mainDocRefList.concat(list);
});
return mainDocRefList;
}),
// tap(lists => console.log(lists)),
switchMap((docRefList: DocumentReference[]) => this.deleteMultipleSimple$(docRefList)),
// catchError((err) => { // TODO super ugly and I dont know why this error is thrown..still works
// if (err === 'Document Does not exists') { return of(null); }
// else { throw err; }
// })
);
}
/**
* Delete all documents and sub collections as specified in subCollectionQueries.
* Not very efficient and causes a lot of db reads.
* If possible use the firebase CLI or the console instead when deleting large collections.
*
* @param collectionRef
* @param subCollectionQueries
*/
public deleteCollection$<T extends DocumentData>(collectionRef: CollectionReference<T>,
subCollectionQueries: SubCollectionQuery[] = []): Observable<any> {
return this.getDocumentReferencesFromCollectionRef$(collectionRef, subCollectionQueries).pipe(
switchMap((docRefs) => this.deleteMultiple$(docRefs))
).pipe(
take(1)
);
}
/**
* Delete firestore document by path
* Convenience method in case we do not have direct access to the AngularFirestoreDocument reference
*
* @param docPath A string representing the path of the referenced document (relative to the root of the database).
* @param subCollectionQueries if the document has child documents the subCollectionQueries are needed to locate them
*/
public deleteDocByPath$(docPath: string, subCollectionQueries: SubCollectionQuery[] = []): Observable<any> {
const docRef = getRefFromPath(docPath, this.fs.firestore) as DocumentReference;
return this.delete$(docRef, subCollectionQueries);
}
/**
* Delete document by FirestoreItem
*
* A very convenient method to remove a previously fetched document.
* Requires that the document/Item is previously fetched since the item needs to be a FireItem, i.e. includes firestoreMetadata.
*
* @param item FirestoreItem to be deleted
* @param subCollectionQueries if the document has child documents the subCollectionQueries are needed to locate them
*/
public deleteItem$<T extends DocumentData>(item: FireItem<T>, subCollectionQueries: SubCollectionQuery[] = []): Observable<any> {
const docRefs = this.getDocumentReferencesFromItem(item, subCollectionQueries);
return this.deleteMultipleSimple$(docRefs).pipe(
// catchError((err) => { // TODO super ugly and I dont know why this error is thrown..still works
// if (err === 'Document Does not exists') { return of(null); }
// else { throw err; }
// }),
take(1)
);
}
/* ---- OTHER ---- */
/**
* clean FirestoreBaseItem properties from the data.
* Usually done if you wish to save the data to firestore, since some FirestoreBaseItem properties are of non allowed types.
*
* Goes through each level and removes DbItemExtras
* In case you wish to save the data
*
* @param data data to be cleaned, either single item or an array of items
* @param subCollectionWriters if the document has child documents the SubCollectionWriters are needed to locate them
* @param additionalFieldsToRemove
*/
cleanExtrasFromData<T>(data: T & DocumentData | FireItem,
subCollectionWriters?: SubCollectionWriter[],
additionalFieldsToRemove?: string[]): T;
cleanExtrasFromData<T>(datas: Array<T & DocumentData | FireItem>,
subCollectionWriters?: SubCollectionWriter[],
additionalFieldsToRemove?: string[]): Array<T>;
public cleanExtrasFromData<T>(data: T & DocumentData | Array<T & DocumentData | FireItem>,
subCollectionWriters: SubCollectionWriter[] = [],
additionalFieldsToRemove: string[] = []): T | Array<T> {
// const dataToBeCleaned = cloneDeep(data); /* clone data so we dont modify the original */
// const dataToBeCleaned = data;
if (Array.isArray(data)) {
const cleanDatas: Array<T> = [];
data.forEach(d => {
cleanDatas.push(
this.removeDataExtrasRecursiveHelper(d, subCollectionWriters, additionalFieldsToRemove) as T
);
});
return cleanDatas;
} else {
return this.removeDataExtrasRecursiveHelper(data, subCollectionWriters, additionalFieldsToRemove) as T;
}
}
/* ---------- PROTECTED METHODS -------------- */
/**
* Same as AngularFirestoreDocument.snapshotChanges but it adds the properties in FirebaseDbItem
* and also allows for to choose action to take when document does not exist
*
* Important to understand this is will trigger for every change/update on the document we are listening to.
*
* @param docRef DocumentReference that will be listened to
* @param actionIfNotExist Action to take if document does not exist
*/
protected listenForDocSimple$<T extends DocumentData>(docRef: DocumentReference<any>,
actionIfNotExist: DocNotExistAction = DocNotExistAction.RETURN_ALL_BUT_DATA
): Observable<FireItem<T>> {
return this.fs.listenForDoc(docRef).pipe(
tap((documentSnapshot: DocumentSnapshot<T>) => {
if (!documentSnapshot.exists() && actionIfNotExist === DocNotExistAction.THROW_DOC_NOT_FOUND) {
const error: FirestoreErrorExt = {
name: 'FirebaseErrorExt',
code: 'not-found',
message: 'Document not found and actionIfNotExist is set to THROW_DOC_NOT_FOUND',
docRef
};
throw error;
}
}),
filter((documentSnapshot: DocumentSnapshot<T>) => {
return !(!documentSnapshot.exists() && actionIfNotExist === DocNotExistAction.FILTER);
}),
map((documentSnapshot: DocumentSnapshot<T>) => {
if (documentSnapshot.exists() || actionIfNotExist === DocNotExistAction.RETURN_ALL_BUT_DATA) {
const data = documentSnapshot.data() as T;
const firestoreMetadata: FirestoreMetadata<T> = {
id: documentSnapshot.id,
ref: documentSnapshot.ref as DocumentReference<T>,
path: docRef.path,
isExists: documentSnapshot.exists(),
documentSnapshot
};
return {...data, firestoreMetadata} as FireItem<T>;
} else if (actionIfNotExist === DocNotExistAction.RETURN_NULL) { /* doc doesn't exist */
return null;
}
return null;
}),
map((data) => {
if (data != null) {
return convertTimestampToDate(data as FireItem<T>);
} else {
return data;
}
}),
) as Observable<FireItem<T>>;
}
/**
* Listens for single collection and returns an array of documents as FireItem<T>[]
* Used internally, please use listenForCollection$() instead.
*
* @param _query the Query which will be listened to
* @protected
*/
protected listenForCollectionSimple$<T extends DocumentData>(_query: Query<T>): Observable<Array<FireItem<T>>> {
/**
* Returns an observable that will emit whenever the ref changes in any way.
* Also adds the id and ref to the object.
*/
return this.fs.listenForCollection(_query).pipe(
map((querySnapshot: QuerySnapshot<T>) => {
return querySnapshot.docs.map(documentSnapshot => {
const data = documentSnapshot.data() as T;
const id = documentSnapshot.id;
const ref = documentSnapshot.ref as DocumentReference<T>;
const path = ref.path;
const firestoreMetadata: FirestoreMetadata<T> = {
id,
path,
ref,
isExists: true,
documentSnapshot
};
return {...data, firestoreMetadata} as FireItem<T>;
});
}),
map((datas: Array<FireItem<T>>) => datas.map((data) => {
return convertTimestampToDate(data as FireItem<T>);
}))
) as Observable<Array<FireItem<T>>>;
}
/**
* Used internally for both listenForDoc and listenForCollection in order to recursively get collections.
*
* Please use listenForDoc or listenForCollection.
*
* @param item
* @param subCollectionQueries
* @protected
*/
protected listenForCollectionsDeep<T extends DocumentData>(
item: FireItem<T>,
subCollectionQueries: SubCollectionQuery[] = []): Observable<FireItem<T>[]> {
if (item == null) {
return of([item]);
}
if (subCollectionQueries.length <= 0) {
return of([item]);
}
const collectionListeners: Array<Observable<any>> = [];
/* Iterate over each sub collection we have given and create collection listeners*/
subCollectionQueries.forEach(subCollectionQuery => {
const queryContainer = new QueryContainer(getSubCollection(item.firestoreMetadata.ref, subCollectionQuery.name));
if (subCollectionQuery.queryConstraints) {
queryContainer.queryConstraints = subCollectionQuery.queryConstraints;
// collectionRef = subCollectionQuery.queryFn(collectionRef) as CollectionReference;
}
// if (subCollectionQuery.queryFn) {
// collectionRef = subCollectionQuery.queryFn(collectionRef) as CollectionReference;
// }
const collectionListener = this.listenForCollectionSimple$(queryContainer.query).pipe(
// filter(docs => docs.length > 0), // skip empty collections or if the subCollectionQuery doesnt exist
/* Uncomment to see data on each update */
// tap(d => console.log(d)),
// filter(docs => docs != null),
/* Listen For and Add any Potential Sub Docs*/
// @ts-ignore // TODO fix this so that I can remove the ts-ignore
mergeMap((items: FireItem[]) => {
if (!subCollectionQuery.subCollections) {
return of(items);
}
const docListeners: Array<Observable<any>> = [];
items = items.filter(d => d != null); // filter out potential nulls
items.forEach((subItem: FireItem) => {
const subDocAndCollections$ = this.listenForCollectionsDeep(subItem, subCollectionQuery.subCollections);
docListeners.push(subDocAndCollections$);
});
if (docListeners.length <= 0) {
return of([]);
} /* subCollectionQuery is empty or doesnt exist */
return combineLatest(docListeners).pipe(
// tap(val => console.log(val))
);
}), /* End of Listening for sub docs */
/* If docs.length === 1 and the id is defaultDocId or the given docId it means we are in a sub subCollectionQuery
and we only care about the data. So we remove the array and just make it one object with the
subCollectionQuery name as key and docs[0] as value */
map((items: FireItem<T>[]) => {
const docId = subCollectionQuery.docId !== undefined ? subCollectionQuery.docId : this.defaultDocId;
if (items.length === 1 && items[0].firestoreMetadata.id === docId) {
return {[subCollectionQuery.name]: items[0]};
} else {
return {[subCollectionQuery.name]: items};
}
}),
// tap(d => console.log(d)),
);
collectionListeners.push(collectionListener);
});
/* Finally return the combined collection listeners*/
// @ts-ignore
return combineLatest(collectionListeners).pipe(
// map((collectionDatas: { [collectionKeyName: string]: FirestoreItem<FirestoreItem<{}>>[] }[]) => {
// map((collectionDatas) => {
map((collectionDatas: { [collectionKeyName: string]: FireItem[] }[]) => {
const datasMap: { [field: string]: any } = {};
collectionDatas.forEach((collectionData) => {
for (const [collectionName, items] of Object.entries(collectionData)) {
datasMap[collectionName] = items;
}
});
return datasMap;
}),
map((data: DocumentData) => {
return {...item, ...data} as T;
}),
);
}
/**
* DO NOT CALL THIS METHOD, meant to be used solely by listenForDocAndSubCollections$
*/
protected listenForDocDeepRecursiveHelper$<T extends DocumentData>(
docRef: DocumentReference<T>,
subCollectionQueries: SubCollectionQuery[] = [],
actionIfNotExist: DocNotExistAction = DocNotExistAction.RETURN_NULL): Observable<any> {
/* Listen for the docFs*/
return this.listenForDocSimple$<T>(docRef, actionIfNotExist).pipe(
mergeMap((item: FireItem<T>) => {
if (item === null) {
return of(item);
}
if (subCollectionQueries.length <= 0) {
return of(item);
}
return this.listenForCollectionsDeep(item, subCollectionQueries);
})
);
}
/**
* A replacement/extension to the AngularFirestoreCollection.add.
* Does the same as AngularFirestoreCollection.add but can also add createdDate and modifiedDate and returns
* the data with the added properties in FirebaseDbItem
*
* Used internally
*
* @param data the data to be added to the document, cannot contain types firestore won't allow
* @param collectionRef the CollectionReference where the document should be added
* @param isAddDates if true adds modifiedDate and createdDate to the data
* @param id if given the added document will be given this id, otherwise a random unique id will be used.
*/
// protected addSimple$<T extends DocumentData>(data: T, collectionRef: CollectionReference<T>, isAddDates: boolean = true, id?: string):
protected addSimple$<T extends DocumentData>(data: T, collectionRef: CollectionReference<T>, id?: string):
Observable<FireItem<T>> {
// let dataToBeSaved: A = Object.assign({}, data);
let res$: Observable<any>;
// if (isAddDates) {
const date = new Date();
data = addCreatedDate(data, false, date);
data = addModifiedDate(data, false, date);
if (id !== undefined) {
const docRef: DocumentReference = getDocRefWithId(collectionRef, id);
res$ = this.fs.set(docRef, data);
} else {
res$ = this.fs.add<T>(collectionRef, data);
}
// if (Array.isArray(data) && isAddDates) {
// data = data.map(item => {
// return {...item, modifiedDate: dataToBeSaved.modifiedDate, createdData: dataToBeSaved.createdData }
// })
// }
res$ = res$.pipe(
// tap(() => this.snackBar.open('Success', 'Added', {duration: 1000})),
// tap(ref => console.log(ref)),
// tap(() => console.log(data)),
map((ref: DocumentReference<T> | undefined) => {
if (id === undefined && ref) {
const path = ref.path;
const firestoreMetadata: FirestoreMetadata<T> = {
id: ref.id,
path,
ref,
isExists: true
};
return {...data, firestoreMetadata} as FireItem<T>;
} else { // if id is defined it means we used docRef.set and ref is undefined
const path = collectionRef.path + '/' + id;
ref = getRefFromPath(path, this.fs.firestore) as DocumentReference<T>;
const firestoreMetadata: FirestoreMetadata<T> = {
id: id as string,
ref,
path,
isExists: true
};
return {...data, firestoreMetadata} as FireItem<T>;
}
}),
);
return res$.pipe(
take(1)
);
}
/** Used internally for updates that doesn't affect child documents */
protected updateSimple$<T>(data: UpdateData<Partial<T>>,
docRef: DocumentReference<T>,
isAddModifiedDate: boolean = true): Observable<void> {
if (isAddModifiedDate) {
data = addModifiedDate(data, false);
}
return this.fs.update<T>(docRef, data);
}
/**
* DO NOT CALL THIS METHOD, used by update deep
*/
protected updateDeepToBatchHelper<T extends DocumentData>(data: UpdateData<T>,
docRef: DocumentReference<T>,
subCollectionWriters: SubCollectionWriter[] = [],
isAddModifiedDate: boolean = true,
batch?: WriteBatch): WriteBatch {
if (batch === undefined) {
batch = writeBatch(this.fs.firestore);
}
if (isAddModifiedDate) {
data = addModifiedDate(data, false);
}
const split = this.splitDataIntoCurrentDocAndSubCollections(data, subCollectionWriters);
const currentDoc = split.currentDoc as UpdateData<T>;
const subCollections = split.subCollections;
// console.log(currentDoc, subCollections);
batch.update(docRef, currentDoc);
for (const [subCollectionKey, subDocUpdateValue] of Object.entries(subCollections)) {
let subSubCollectionWriters: SubCollectionWriter[] | undefined; // undefined if no subCollectionWriters
let subDocId: string | undefined;
if (subCollectionWriters) {
subSubCollectionWriters = subCollectionWriters.find(subColl => subColl.name === subCollectionKey)?.subCollections;
subDocId = subCollectionWriters.find(subColl => subColl.name === subCollectionKey)?.docId;
}
subDocId = subDocId !== undefined ? subDocId : this.defaultDocId; /* Set default if none given */
// const subDocFs = docRef.collection(subCollectionKey).doc(subDocId);
const subCollection = getSubCollection(docRef, subCollectionKey);
const subDocFs = getDocRefWithId(subCollection, subDocId);
batch = this.updateDeepToBatchHelper(subDocUpdateValue, subDocFs, subSubCollectionWriters, isAddModifiedDate, batch);
}
return batch;
}
/**
* Used mainly for drag and drop scenarios where we drag an item from one list to another and the the docs
* have an index value and a groupName.
*
* @param previousArray
* @param currentArray
* @param previousIndex
* @param currentIndex
* @param currentArrayName
* @param additionalDataUpdateOnMovedItem
* @param isUpdateModifiedDateOnMovedItem
* @param useCopy
* @protected
*/
protected getBatchFromTransferItemInIndexedDocs<T extends DocumentData & ItemWithIndexGroup>(
previousArray: Array<FireItem<T>>,
currentArray: Array<FireItem<T>>,
previousIndex: number,
currentIndex: number,
currentArrayName: string,
additionalDataUpdateOnMovedItem?: { [key: string]: any },
isUpdateModifiedDateOnMovedItem = true,
useCopy = false): WriteBatch {
let usedPreviousArray: Array<FireItem<T>>;
let usedCurrentArray: Array<FireItem<T>>;
if (useCopy) {
usedPreviousArray = Object.assign([], previousArray);
usedCurrentArray = Object.assign([], currentArray);
} else {
usedPreviousArray = previousArray;
usedCurrentArray = currentArray;
}
transferArrayItem(usedPreviousArray, usedCurrentArray, previousIndex, currentIndex);
const batch: WriteBatch = writeBatch(this.fs.firestore);
if (additionalDataUpdateOnMovedItem !== undefined) {
const movedItem = usedCurrentArray[currentIndex];
const movedItemRef = movedItem.firestoreMetadata.ref;
const data = {...additionalDataUpdateOnMovedItem, groupName: currentArrayName};
if (!useCopy) {
addDataToItem(movedItem, data, true);
}
if (isUpdateModifiedDateOnMovedItem) {
const date = new Date();
addModifiedDate(data, true, date);
if (!useCopy) {
addModifiedDate(movedItem, true, date);
}
}
batch.update(movedItemRef, data);
}
const currentArraySliceToUpdate: Array<FireItem<T>> = usedCurrentArray.slice(currentIndex);
let i = currentIndex;
for (const item of currentArraySliceToUpdate) {
// @ts-ignore
batch.update(item.firestoreMetadata.ref, {index: i});
if (!useCopy) {
item.index = i;
}
i++;
}
const prevArraySliceToUpdate: Array<FireItem<T>> = usedPreviousArray.slice(previousIndex);
i = previousIndex;
for (const item of prevArraySliceToUpdate) {
// @ts-ignore
batch.update(item.firestoreMetadata.ref, {index: i});
if (!useCopy) {
item.index = i;
}
i++;
}
return batch;
}
/**
* Delete Documents
*
* @param docRefs - A list of DocumentReference that are to be deleted
*/
protected deleteMultipleSimple$(docRefs: DocumentReference[]): Observable<void> {
const batch = this.getDeleteMultipleSimpleBatch(docRefs);
return this.batchCommit$(batch);
}
protected getDeleteMultipleSimpleBatch(docRefs: DocumentReference[], batch: WriteBatch = writeBatch(this.fs.firestore)): WriteBatch {
docRefs.forEach((docRef) => {
batch.delete(docRef);
});
return batch;
}
/**
* Recursive method to clean FirestoreBaseItem properties from the dbItem
*
* @param dbItem the data to be cleaned
* @param subCollectionWriters list of SubCollectionWriters to handle sub collections
* @param additionalFieldsToRemove
*/
protected removeDataExtrasRecursiveHelper<T extends DocumentData>(dbItem: T,
subCollectionWriters: SubCollectionWriter[] = [],
additionalFieldsToRemove: string[] = []): T {
// const extraPropertyNames: string[] = Object.getOwnPropertyNames(new DbItemExtras());
const extraPropertyNames: string[] = ['firestoreMetadata'].concat(additionalFieldsToRemove);
/* Current level delete */
for (const extraPropertyName of extraPropertyNames) {
delete dbItem[extraPropertyName];
}
subCollectionWriters.forEach(col => {
if (Array.isArray(dbItem[col.name])) { /* property is array so will contain multiple docs */
const docs: T[] = dbItem[col.name];
docs.forEach((d, i) => {
if (col.subCollections) {
this.removeDataExtrasRecursiveHelper(d, col.subCollections, additionalFieldsToRemove);
} else {
/* */
for (const extraPropertyName of extraPropertyNames) {
delete dbItem[col.name][i][extraPropertyName];
}
}
});
} else { /* not an array so a single doc*/
if (col.subCollections) {
this.removeDataExtrasRecursiveHelper(dbItem[col.name], col.subCollections, additionalFieldsToRemove);
} else {
for (const extraPropertyName of extraPropertyNames) {
delete dbItem[col.name][extraPropertyName];
}
}
}
});
return dbItem;
}
/**
* Returns an Observable containing a list of DocumentReference found under the given docRef using the SubCollectionQuery[]
* Mainly used to delete a docFs and its sub docs
* @param ref: DocumentReference | CollectionReference
* @param subCollectionQueries: SubCollectionQuery[]
*/
protected getDocumentReferencesDeep$(ref: DocumentReference | CollectionReference,
subCollectionQueries: SubCollectionQuery[] = []):
Observable<DocumentReference[]> {
if (ref instanceof DocumentReference) {
return this.getDocumentReferencesFromDocRef$<FireItem>(ref as DocumentReference<FireItem>, subCollectionQueries);
} else { // CollectionReference
return this.getDocumentReferencesFromCollectionRef$(ref as CollectionReference<FireItem>, subCollectionQueries);
}
}
protected getDocumentReferencesFromDocRef$<T extends FireItem>(docRef: DocumentReference<T>,
subCollectionQueries: SubCollectionQuery[] = []):
Observable<DocumentReference[]> {
return this.listenForDoc$<T>(docRef, subCollectionQueries).pipe(
take(1),
map((item: FireItem<T>) => this.getPathsFromItemDeepRecursiveHelper(item, subCollectionQueries)),
// tap(pathList => console.log(pathList)),
map((pathList: string[]) => {
return pathList
.map(path => getRefFromPath(path, this.fs.firestore) as DocumentReference);
}),
// tap(item => console.log(item)),
);
}
protected getDocumentReferencesFromCollectionRef$<T extends DocumentData>(collectionRef: CollectionReference<T>,
subCollectionQueries: SubCollectionQuery[] = []):
Observable<DocumentReference[]> {
return this.listenForCollectionSimple$(collectionRef).pipe(
// @ts-ignore
take(1),
mergeMap((items: FireItem[]) => {
let docListeners: Array<Observable<any>>;
docListeners = items.map(item => this.listenForDoc$(item.firestoreMetadata.ref, subCollectionQueries));
return combineLatest(docListeners);
}),
map((items: FireItem[]) => {
let paths: string[] = [];
items.forEach(item => {
paths = paths.concat(this.getPathsFromItemDeepRecursiveHelper(item, subCollectionQueries));
});
return paths;
}),
map((pathList: string[]) => {
return pathList
.map(path => getRefFromPath(path, this.fs.firestore) as DocumentReference);
}),
);
}
/**
* Used by deleteDeepByItem$ to get all the AngularFirestoreDocuments to be deleted
* including child documents using SubCollectionQueries
*
* Internal use
* @param item FirestoreItem from where we get the AngularFirestoreDocuments
* @param subCollectionQueries if the dbItem has child documents the subCollectionQueries are needed to locate them
*/
protected getDocumentReferencesFromItem<T extends DocumentData>(
item: FireItem<T>,
subCollectionQueries: SubCollectionQuery[] = []): DocumentReference[] {
const paths = this.getPathsFromItemDeepRecursiveHelper(item, subCollectionQueries);
return paths.map(path => getRefFromPath(path, this.fs.firestore) as DocumentReference);
}
/**
* DO NOT CALL THIS METHOD, its meant as a support method for getDocs$
*/
protected getPathsFromItemDeepRecursiveHelper<T extends FireItem>(item: T,
subCollectionQueries: SubCollectionQuery[] = []): string[] {
if (subCollectionQueries == null || subCollectionQueries.length === 0) {
return [item.firestoreMetadata.path];
}
let pathList: string[] = [];
pathList.push(item.firestoreMetadata.path);
subCollectionQueries.forEach(col => {
if (Array.isArray((item as DocumentData)[col.name]) && !col.docId) {
/* property is array and not using docId so will contain multiple docs */
const items: T[] = (item as DocumentData)[col.name];
items.forEach(subItem => {
if (col.subCollections) {
pathList = pathList.concat(this.getPathsFromItemDeepRecursiveHelper(subItem, col.subCollections));
} else {
pathList.push(subItem.firestoreMetadata.path);
}
});
} else { /* not an array so a single doc*/
if (col.subCollections) {
pathList = pathList.concat(this.getPathsFromItemDeepRecursiveHelper(item, col.subCollections));
} else {
const subItem = ((item as DocumentData)[col.name] as FireItem);
if (subItem != null && 'path' in subItem.firestoreMetadata) {
pathList.push(subItem.firestoreMetadata.path);
}
// const path = (dbItem[col.name] as FirestoreItem).path;
}
}
});
return pathList;
}
/**
* DO NOT CALL THIS METHOD, used in addDeep and updateDeep to split the data into currentDoc and subCollections
* Only goes one sub level deep;
*/
protected splitDataIntoCurrentDocAndSubCollections<T>(
data: T,
subCollectionWriters: SubCollectionWriter[] = []): CurrentDocSubCollectionSplit {
/* Split data into current doc and sub collections */
let currentDoc: { [index: string]: any; } = {};
const subCollections: { [index: string]: any; } = {};
/* Check if the key is in subCollections, if it is place it in subCollections else place it in currentDoc */
// not array so object
for (const [key, value] of Object.entries(data)) {
// console.log(key, value);
if (subCollectionWriters && subCollectionWriters.length > 0) {
const subCollectionWriter: SubCollectionWriter | undefined = subCollectionWriters.find(subColl => subColl.name === key);
if (subCollectionWriter) {
subCollections[key] = value;
} else {
currentDoc[key] = value;
}
} else {
currentDoc = data;
}
}
return {
currentDoc,
subCollections
} as CurrentDocSubCollectionSplit;
}
/**
* Turn a batch into an Observable instead of Promise.
*
* For some reason angularfire returns a promise on batch.commit() instead of an observable like for
* everything else.
*
* This method turns it into an observable
*/
protected batchCommit$(batch: WriteBatch): Observable<void> {
return from(batch.commit()).pipe(
take(1)
);
}
}
/**
* Firebase version 9 changed the query syntax
* The new syntax broken the ability to chain queries like this:
*
* collectionRef.where('foo', '==', 123).limit(10)..returns the collection ref
*
* now instead you must write it like this, query(collectionRef, where('foo', '==', 123), limit(10))...returns a Query
*
* which is ugly and make you loose the information that was present in the collectionRef since a Query is returned instead,
* which holds less information than a CollectionReference.
*
* This Container is meant to allow you to chain queries, like before version 9 and also retain the information in
* the original CollectionReference
*/
export class QueryContainer<T> {
public queryConstraints: QueryConstraint[] = [];
constructor(public ref: CollectionReference<T>) {
}
/** factory method to create container from path */
static fromPath<T>(firestore: Firestore, path: string): QueryContainer<T> {
const ref = collection(firestore, path) as CollectionReference<T>;
return new this(ref);
}
/** Returns the query with all the query constraints */
get query(): Query<T> {
return query(this.ref, ...this.queryConstraints);
}
/** Calls the firebase getDocs() method and listens for the documents in the query. */
getDocs$(): Observable<QuerySnapshot<T>> {
return from(getDocs<T>(this.query));
}
where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryContainer<T> {
this.queryConstraints.push(where(fieldPath, opStr, value));
return this;
}
orderBy(fieldPath: string | FieldPath, directionStr?: OrderByDirection): QueryContainer<T> {
this.queryConstraints.push(orderBy(fieldPath, directionStr));
return this;
}
limit(_limit: number): QueryContainer<T> {
this.queryConstraints.push(limit(_limit));
return this;
}
limitToLast(_limit: number): QueryContainer<T> {
this.queryConstraints.push(limitToLast(_limit));
return this;
}
startAt(...fieldValues: unknown[]): QueryContainer<T>; // definition
startAt(snapshot?: DocumentSnapshot<unknown>): QueryContainer<T>; // definition
startAt(snapshot?: DocumentSnapshot<unknown>, ...fieldValues: unknown[]): QueryContainer<T> { // implementation
if (snapshot) {
this.queryConstraints.push(startAt(snapshot));
} else if (fieldValues) {
this.queryConstraints.push(startAt(...fieldValues));
}
return this;
}
startAfter(...fieldValues: unknown[]): QueryContainer<T>; // definition
startAfter(snapshot?: DocumentSnapshot<unknown>): QueryContainer<T>; // definition
startAfter(snapshot?: DocumentSnapshot<unknown>, ...fieldValues: unknown[]): QueryContainer<T> { // implementation
if (snapshot) {
this.queryConstraints.push(startAfter(snapshot));
} else if (fieldValues) {
this.queryConstraints.push(startAfter(...fieldValues));
}
return this;
}
endAt(...fieldValues: unknown[]): QueryContainer<T>; // definition
endAt(snapshot?: DocumentSnapshot<unknown>): QueryContainer<T>; // definition
endAt(snapshot?: DocumentSnapshot<unknown>, ...fieldValues: unknown[]): QueryContainer<T> { // implementation
if (snapshot) {
this.queryConstraints.push(endAt(snapshot));
} else if (fieldValues) {
this.queryConstraints.push(endAt(...fieldValues));
}
return this;
}
endBefore(...fieldValues: unknown[]): QueryContainer<T>; // definition
endBefore(snapshot?: DocumentSnapshot<unknown>): QueryContainer<T>; // definition
endBefore(snapshot?: DocumentSnapshot<unknown>, ...fieldValues: unknown[]): QueryContainer<T> { // implementation
if (snapshot) {
this.queryConstraints.push(endBefore(snapshot));
} else if (fieldValues) {
this.queryConstraints.push(endBefore(...fieldValues));
}
return this;
}
}