UNPKG

@angular/fire

Version:

The official library for Firebase and Angular

790 lines (772 loc) 22.9 kB
import { InjectionToken, Injectable, Inject, Optional, PLATFORM_ID, NgZone, ɵɵdefineInjectable, ɵɵinject, NgModule } from '@angular/core'; import { asyncScheduler, Observable, of, merge } from 'rxjs'; import { map, share, switchMap, scan, distinctUntilChanged, withLatestFrom, skipWhile } from 'rxjs/operators'; import { ɵAngularFireSchedulers, ɵkeepUnstableUntilFirstFactory, ɵfirebaseAppFactory, FIREBASE_OPTIONS, FIREBASE_APP_NAME } from '@angular/fire'; import 'firebase/database'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @param {?} value * @return {?} */ function isString(value) { return typeof value === 'string'; } /** * @param {?} value * @return {?} */ function isFirebaseDataSnapshot(value) { return typeof value.exportVal === 'function'; } /** * @param {?} obj * @return {?} */ function isNil(obj) { return obj === undefined || obj === null; } /** * @param {?} value * @return {?} */ function isFirebaseRef(value) { return typeof value.set === 'function'; } /** * Returns a database reference given a Firebase App and an * absolute or relative path. * @param {?} database - Firebase Database * @param {?} pathRef - Database path, relative or absolute * @return {?} */ function getRef(database, pathRef) { // if a db ref was passed in, just return it return isFirebaseRef(pathRef) ? (/** @type {?} */ (pathRef)) : database.ref((/** @type {?} */ (pathRef))); } /** * @param {?} item * @param {?} cases * @return {?} */ function checkOperationCases(item, cases) { if (isString(item)) { return cases.stringCase(); } else if (isFirebaseRef(item)) { return cases.firebaseCase(); } else if (isFirebaseDataSnapshot(item)) { return cases.snapshotCase(); } throw new Error(`Expects a string, snapshot, or reference. Got: ${typeof item}`); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @record * @template T */ function SnapshotPrevKey() { } if (false) { /** @type {?} */ SnapshotPrevKey.prototype.snapshot; /** @type {?} */ SnapshotPrevKey.prototype.prevKey; } /** * Create an observable from a Database Reference or Database Query. * @template T * @param {?} ref Database Reference * @param {?} event Listen event type ('value', 'added', 'changed', 'removed', 'moved') * @param {?=} listenType 'on' or 'once' * @param {?=} scheduler - Rxjs scheduler * @return {?} */ function fromRef(ref, event, listenType = 'on', scheduler = asyncScheduler) { return new Observable((/** * @param {?} subscriber * @return {?} */ subscriber => { /** @type {?} */ let fn = null; fn = ref[listenType](event, (/** * @param {?} snapshot * @param {?} prevKey * @return {?} */ (snapshot, prevKey) => { scheduler.schedule((/** * @return {?} */ () => { subscriber.next({ snapshot, prevKey }); })); if (listenType === 'once') { scheduler.schedule((/** * @return {?} */ () => subscriber.complete())); } }), (/** * @param {?} err * @return {?} */ err => { scheduler.schedule((/** * @return {?} */ () => subscriber.error(err))); })); if (listenType === 'on') { return { /** * @return {?} */ unsubscribe() { if (fn != null) { ref.off(event, fn); } } }; } else { return { /** * @return {?} */ unsubscribe() { } }; } })).pipe(map((/** * @param {?} payload * @return {?} */ payload => { const { snapshot, prevKey } = payload; /** @type {?} */ let key = null; if (snapshot.exists()) { key = snapshot.key; } return { type: event, payload: snapshot, prevKey, key }; })), share()); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @template T * @param {?} ref * @param {?} events * @param {?=} scheduler * @return {?} */ function listChanges(ref, events, scheduler) { return fromRef(ref, 'value', 'once', scheduler).pipe(switchMap((/** * @param {?} snapshotAction * @return {?} */ snapshotAction => { /** @type {?} */ const childEvent$ = [of(snapshotAction)]; events.forEach((/** * @param {?} event * @return {?} */ event => childEvent$.push(fromRef(ref, event, 'on', scheduler)))); return merge(...childEvent$).pipe(scan(buildView, [])); })), distinctUntilChanged()); } /** * @template T * @param {?} changes * @param {?} key * @return {?} */ function positionFor(changes, key) { /** @type {?} */ const len = changes.length; for (let i = 0; i < len; i++) { if (changes[i].payload.key === key) { return i; } } return -1; } /** * @template T * @param {?} changes * @param {?=} prevKey * @return {?} */ function positionAfter(changes, prevKey) { if (isNil(prevKey)) { return 0; } else { /** @type {?} */ const i = positionFor(changes, prevKey); if (i === -1) { return changes.length; } else { return i + 1; } } } /** * @param {?} current * @param {?} action * @return {?} */ function buildView(current, action) { const { payload, prevKey, key } = action; /** @type {?} */ const currentKeyPosition = positionFor(current, key); /** @type {?} */ const afterPreviousKeyPosition = positionAfter(current, prevKey); switch (action.type) { case 'value': if (action.payload && action.payload.exists()) { /** @type {?} */ let prevKey = null; action.payload.forEach((/** * @param {?} payload * @return {?} */ payload => { /** @type {?} */ const action = { payload, type: 'value', prevKey, key: payload.key }; prevKey = payload.key; current = [...current, action]; return false; })); } return current; case 'child_added': if (currentKeyPosition > -1) { // check that the previouskey is what we expect, else reorder /** @type {?} */ const previous = current[currentKeyPosition - 1]; if ((previous && previous.key || null) !== prevKey) { current = current.filter((/** * @param {?} x * @return {?} */ x => x.payload.key !== payload.key)); current.splice(afterPreviousKeyPosition, 0, action); } } else if (prevKey == null) { return [action, ...current]; } else { current = current.slice(); current.splice(afterPreviousKeyPosition, 0, action); } return current; case 'child_removed': return current.filter((/** * @param {?} x * @return {?} */ x => x.payload.key !== payload.key)); case 'child_changed': return current.map((/** * @param {?} x * @return {?} */ x => x.payload.key === key ? action : x)); case 'child_moved': if (currentKeyPosition > -1) { /** @type {?} */ const data = current.splice(currentKeyPosition, 1)[0]; current = current.slice(); current.splice(afterPreviousKeyPosition, 0, data); return current; } return current; // default will also remove null results default: return current; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @param {?=} events * @return {?} */ function validateEventsArray(events) { if (isNil(events) || events.length === 0) { events = ['child_added', 'child_removed', 'child_changed', 'child_moved']; } return events; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @template T * @param {?} query * @param {?=} events * @param {?=} scheduler * @return {?} */ function snapshotChanges(query, events, scheduler) { events = validateEventsArray(events); return listChanges(query, events, scheduler); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @template T * @param {?} query * @param {?=} events * @param {?=} scheduler * @return {?} */ function stateChanges(query, events, scheduler) { events = validateEventsArray(events); /** @type {?} */ const childEvent$ = events.map((/** * @param {?} event * @return {?} */ event => fromRef(query, event, 'on', scheduler))); return merge(...childEvent$); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @template T * @param {?} query * @param {?=} events * @param {?=} scheduler * @return {?} */ function auditTrail(query, events, scheduler) { /** @type {?} */ const auditTrail$ = stateChanges(query, events) .pipe(scan((/** * @param {?} current * @param {?} action * @return {?} */ (current, action) => [...current, action]), [])); return waitForLoaded(query, auditTrail$, scheduler); } /** * @record */ function LoadedMetadata() { } if (false) { /** @type {?} */ LoadedMetadata.prototype.data; /** @type {?} */ LoadedMetadata.prototype.lastKeyToLoad; } /** * @template T * @param {?} query * @param {?=} scheduler * @return {?} */ function loadedData(query, scheduler) { // Create an observable of loaded values to retrieve the // known dataset. This will allow us to know what key to // emit the "whole" array at when listening for child events. return fromRef(query, 'value', 'on', scheduler) .pipe(map((/** * @param {?} data * @return {?} */ data => { // Store the last key in the data set /** @type {?} */ let lastKeyToLoad; // Loop through loaded dataset to find the last key data.payload.forEach((/** * @param {?} child * @return {?} */ child => { lastKeyToLoad = child.key; return false; })); // return data set and the current last key loaded return { data, lastKeyToLoad }; }))); } /** * @template T * @param {?} query * @param {?} action$ * @param {?=} scheduler * @return {?} */ function waitForLoaded(query, action$, scheduler) { /** @type {?} */ const loaded$ = loadedData(query, scheduler); return loaded$ .pipe(withLatestFrom(action$), // Get the latest values from the "loaded" and "child" datasets // We can use both datasets to form an array of the latest values. map((/** * @param {?} __0 * @return {?} */ ([loaded, actions]) => { // Store the last key in the data set /** @type {?} */ const lastKeyToLoad = loaded.lastKeyToLoad; // Store all child keys loaded at this point /** @type {?} */ const loadedKeys = actions.map((/** * @param {?} snap * @return {?} */ snap => snap.key)); return { actions, lastKeyToLoad, loadedKeys }; })), // This is the magical part, only emit when the last load key // in the dataset has been loaded by a child event. At this point // we can assume the dataset is "whole". skipWhile((/** * @param {?} meta * @return {?} */ meta => meta.loadedKeys.indexOf(meta.lastKeyToLoad) === -1)), // Pluck off the meta data because the user only cares // to iterate through the snapshots map((/** * @param {?} meta * @return {?} */ meta => meta.actions))); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @template T * @param {?} ref * @param {?} operation * @return {?} */ function createDataOperationMethod(ref, operation) { return (/** * @template T * @param {?} item * @param {?} value * @return {?} */ function dataOperation(item, value) { return checkOperationCases(item, { stringCase: (/** * @return {?} */ () => ref.child((/** @type {?} */ (item)))[operation](value)), firebaseCase: (/** * @return {?} */ () => ((/** @type {?} */ (item)))[operation](value)), snapshotCase: (/** * @return {?} */ () => ((/** @type {?} */ (item))).ref[operation](value)) }); }); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ // TODO(davideast): Find out why TS thinks this returns firebase.Primise // instead of Promise. /** * @template T * @param {?} ref * @return {?} */ function createRemoveMethod(ref) { return (/** * @param {?=} item * @return {?} */ function remove(item) { if (!item) { return ref.remove(); } return checkOperationCases(item, { stringCase: (/** * @return {?} */ () => ref.child((/** @type {?} */ (item))).remove()), firebaseCase: (/** * @return {?} */ () => ((/** @type {?} */ (item))).remove()), snapshotCase: (/** * @return {?} */ () => ((/** @type {?} */ (item))).ref.remove()) }); }); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @template T * @param {?} query * @param {?} afDatabase * @return {?} */ function createListReference(query, afDatabase) { /** @type {?} */ const outsideAngularScheduler = afDatabase.schedulers.outsideAngular; /** @type {?} */ const refInZone = afDatabase.schedulers.ngZone.run((/** * @return {?} */ () => query.ref)); return { query, update: createDataOperationMethod(refInZone, 'update'), set: createDataOperationMethod(refInZone, 'set'), push: (/** * @param {?} data * @return {?} */ (data) => refInZone.push(data)), remove: createRemoveMethod(refInZone), /** * @param {?=} events * @return {?} */ snapshotChanges(events) { return snapshotChanges(query, events, outsideAngularScheduler).pipe(afDatabase.keepUnstableUntilFirst); }, /** * @param {?=} events * @return {?} */ stateChanges(events) { return stateChanges(query, events, outsideAngularScheduler).pipe(afDatabase.keepUnstableUntilFirst); }, /** * @param {?=} events * @return {?} */ auditTrail(events) { return auditTrail(query, events, outsideAngularScheduler).pipe(afDatabase.keepUnstableUntilFirst); }, /** * @param {?=} events * @return {?} */ valueChanges(events) { /** @type {?} */ const snapshotChanges$ = snapshotChanges(query, events, outsideAngularScheduler); return snapshotChanges$.pipe(map((/** * @param {?} actions * @return {?} */ actions => actions.map((/** * @param {?} a * @return {?} */ a => (/** @type {?} */ (a.payload.val())))))), afDatabase.keepUnstableUntilFirst); } }; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @template T * @param {?} query * @param {?=} scheduler * @return {?} */ function createObjectSnapshotChanges(query, scheduler) { return (/** * @return {?} */ function snapshotChanges() { return fromRef(query, 'value', 'on', scheduler); }); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @template T * @param {?} query * @param {?} afDatabase * @return {?} */ function createObjectReference(query, afDatabase) { return { query, /** * @template T * @return {?} */ snapshotChanges() { return createObjectSnapshotChanges(query, afDatabase.schedulers.outsideAngular)().pipe(afDatabase.keepUnstableUntilFirst); }, /** * @param {?} data * @return {?} */ update(data) { return (/** @type {?} */ (query.ref.update((/** @type {?} */ (data))))); }, /** * @param {?} data * @return {?} */ set(data) { return (/** @type {?} */ (query.ref.set(data))); }, /** * @return {?} */ remove() { return (/** @type {?} */ (query.ref.remove())); }, /** * @template T * @return {?} */ valueChanges() { /** @type {?} */ const snapshotChanges$ = createObjectSnapshotChanges(query, afDatabase.schedulers.outsideAngular)(); return snapshotChanges$.pipe(afDatabase.keepUnstableUntilFirst, map((/** * @param {?} action * @return {?} */ action => action.payload.exists() ? (/** @type {?} */ (action.payload.val())) : null))); }, }; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @type {?} */ const URL = new InjectionToken('angularfire2.realtimeDatabaseURL'); class AngularFireDatabase { /** * @param {?} options * @param {?} nameOrConfig * @param {?} databaseURL * @param {?} platformId * @param {?} zone */ constructor(options, nameOrConfig, databaseURL, // tslint:disable-next-line:ban-types platformId, zone) { this.schedulers = new ɵAngularFireSchedulers(zone); this.keepUnstableUntilFirst = ɵkeepUnstableUntilFirstFactory(this.schedulers); this.database = zone.runOutsideAngular((/** * @return {?} */ () => { /** @type {?} */ const app = ɵfirebaseAppFactory(options, zone, nameOrConfig); return app.database(databaseURL || undefined); })); } /** * @template T * @param {?} pathOrRef * @param {?=} queryFn * @return {?} */ list(pathOrRef, queryFn) { /** @type {?} */ const ref = getRef(this.database, pathOrRef); /** @type {?} */ let query = ref; if (queryFn) { query = queryFn(ref); } return createListReference(query, this); } /** * @template T * @param {?} pathOrRef * @return {?} */ object(pathOrRef) { /** @type {?} */ const ref = getRef(this.database, pathOrRef); return createObjectReference(ref, this); } /** * @return {?} */ createPushId() { return this.database.ref().push().key; } } AngularFireDatabase.decorators = [ { type: Injectable, args: [{ providedIn: 'any' },] } ]; /** @nocollapse */ AngularFireDatabase.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [FIREBASE_OPTIONS,] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [FIREBASE_APP_NAME,] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [URL,] }] }, { type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }, { type: NgZone } ]; /** @nocollapse */ AngularFireDatabase.ɵprov = ɵɵdefineInjectable({ factory: function AngularFireDatabase_Factory() { return new AngularFireDatabase(ɵɵinject(FIREBASE_OPTIONS), ɵɵinject(FIREBASE_APP_NAME, 8), ɵɵinject(URL, 8), ɵɵinject(PLATFORM_ID), ɵɵinject(NgZone)); }, token: AngularFireDatabase, providedIn: "any" }); if (false) { /** @type {?} */ AngularFireDatabase.prototype.database; /** @type {?} */ AngularFireDatabase.prototype.schedulers; /** @type {?} */ AngularFireDatabase.prototype.keepUnstableUntilFirst; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class AngularFireDatabaseModule { } AngularFireDatabaseModule.decorators = [ { type: NgModule, args: [{ providers: [AngularFireDatabase] },] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ export { AngularFireDatabase, AngularFireDatabaseModule, URL, auditTrail, createListReference, fromRef, listChanges, snapshotChanges, stateChanges }; //# sourceMappingURL=angular-fire-database.js.map