Press n or j to go to the next uncovered block, b, p or k for the previous block.
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 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | 1x 1x 1x 1x 1x 1x 1x 51x 51x 51x 51x 51x 51x 51x 504x 504x 503x 504x 504x 504x 504x 504x 58x 9x 9x 8x 8x 8x 1x 505x 505x 505x 9x 9x 8x 1x 512x 110x 402x 402x 402x 1701x 1701x 1701x 1701x 402x 402x 402x 402x 47x | import { Inject, Injectable, NgZone } from '@angular/core'; import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore'; import { LoadingBarService } from '@ngx-loading-bar/core'; import { BehaviorSubject, Observable } from 'rxjs'; import { scan, take, tap } from 'rxjs/operators'; import { APP_CONFIG, InterfaceAppConfig } from '../app-config'; /** * options to reproduce firestore queries consistently */ interface QueryConfig { /** path to collection */ path: string; /** field to orderBy */ field: string; /** limit per query */ limit?: number; /** reverse order? */ reverse?: boolean; /** prepend to source? */ prepend?: boolean; } /** * Pagination Service */ @Injectable() export class PaginationService { /** observable data */ data: Observable<any>; /** is done? */ done = new BehaviorSubject<boolean>(false); /** is loading? */ loading = new BehaviorSubject<boolean>(false); /** private source data */ private readonly _data = new BehaviorSubject([]); /** QueryConfig */ private query: QueryConfig; /** * constructor of PaginationService * @param afs: AngularFirestore * @param ngZone: NgZone * @param loadingBar: LoadingBarService * @param appConfig: APP_CONFIG */ constructor(private readonly afs: AngularFirestore, private readonly ngZone: NgZone, private readonly loadingBar: LoadingBarService, @Inject(APP_CONFIG) private readonly appConfig: InterfaceAppConfig) { } /** * initial query sets options and defines the Observable * @param path: path to collection * @param field: field to orderBy * @param opts: options * @param isReset: do you want to reset before init? */ init(path, field, opts?, isReset?): void { this.query = { path, field, limit: 2, reverse: false, prepend: false, ...opts }; if (isReset || isReset === undefined) { this.reset(); } setTimeout(() => { const first = this.afs.collection(this.query.path, ref => ref .orderBy(this.query.field, this.query.reverse ? 'desc' : 'asc') .limit(this.query.limit)); this.mapAndUpdate(first); // create the observable array for consumption in components this.data = this._data.asObservable() .pipe(scan((acc, val) => this.query.prepend ? val.concat(acc) : acc.concat(val))); }, 0); } /** * Retrieves additional data from firestore */ more(): any { const cursor = this.getCursor(); if (cursor) { const more = this.afs.collection(this.query.path, ref => ref .orderBy(this.query.field, this.query.reverse ? 'desc' : 'asc') .limit(this.query.limit) .startAfter(cursor)); this.mapAndUpdate(more); } else { this.done.next(true); } } /** * Reset the pagination */ reset(): void { this._data.next([]); this.done.next(false); // istanbul ignore next if (!this.appConfig.isUnitTest) { // I do not want to add 'tick(x)' to all of end of unit test cases just for loading bar this.loadingBar.complete(); } this.loading.next(false); } /** * Determines the doc snapshot to paginate query */ private getCursor(): any { const current = this._data.value; if (current.length) { return this.query.prepend ? current[0].doc : current[current.length - 1].doc; } return; } /** * Maps the snapshot to usable format the updates source * @param col: AngularFirestoreCollection */ private mapAndUpdate(col: AngularFirestoreCollection<any>): any { if (this.done.value || this.loading.value) { return; } // loading // istanbul ignore next if (!this.appConfig.isUnitTest) { // I do not want to add 'tick(x)' to all of end of unit test cases just for loading bar this.loadingBar.start(); } this.loading.next(true); // map snapshot with doc ref (needed for cursor) return col.snapshotChanges() .pipe(tap(arr => { let values = arr.map(snap => { const id = snap.payload.doc.id; const data = snap.payload.doc.data(); const doc = snap.payload.doc; return {id, ...data, doc}; }); // if prepending, reverse array values = this.query.prepend ? values.reverse() : values; // update source with new values, done loading this._data.next(values); // istanbul ignore next if (!this.appConfig.isUnitTest) { // I do not want to add 'tick(x)' to all of end of unit test cases just for loading bar this.loadingBar.complete(); } this.loading.next(false); // no more values, mark done if (!values.length) { this.done.next(true); } })) .pipe(take(1)) .subscribe(); } } |