From 03abbeda62d91fe2e6a344ff54bc37d79ff5c157 Mon Sep 17 00:00:00 2001 From: Nicholas Jamieson Date: Fri, 10 Feb 2017 13:12:52 +1000 Subject: [PATCH] fix(database): retrieve initial list content once Closes #819 --- src/database/firebase_list_factory.ts | 131 +++++++++++--------------- 1 file changed, 57 insertions(+), 74 deletions(-) diff --git a/src/database/firebase_list_factory.ts b/src/database/firebase_list_factory.ts index d50491a2d..626117c5a 100644 --- a/src/database/firebase_list_factory.ts +++ b/src/database/firebase_list_factory.ts @@ -108,88 +108,71 @@ export function FirebaseListFactory ( * is loaded, the observable starts emitting values. */ function firebaseListObservable(ref: firebase.database.Reference | firebase.database.Query, {preserveSnapshot}: FirebaseListFactoryOpts = {}): FirebaseListObservable { + const toValue = preserveSnapshot ? (snapshot => snapshot) : utils.unwrapMapFn; const toKey = preserveSnapshot ? (value => value.key) : (value => value.$key); - // Keep track of callback handles for calling ref.off(event, handle) - const handles = []; + const listObs = new FirebaseListObservable(ref, (obs: Observer) => { - ref.once('value') - .then((snap) => { - let initialArray = []; - snap.forEach(child => { - initialArray.push(toValue(child)) - }); - return initialArray; - }) - .then((initialArray) => { - const isInitiallyEmpty = initialArray.length === 0; - let hasInitialLoad = false; - let lastKey; - - if (!isInitiallyEmpty) { - // The last key in the initial array tells us where - // to begin listening in realtime - lastKey = toKey(initialArray[initialArray.length - 1]); - } - const addFn = ref.on('child_added', (child: any, prevKey: string) => { - // If the initial load has not been set and the current key is - // the last key of the initialArray, we know we have hit the - // initial load - if (!isInitiallyEmpty && !hasInitialLoad) { - if (child.key === lastKey) { - hasInitialLoad = true; - obs.next(initialArray); - return; - } - } - - if (hasInitialLoad) { - initialArray = onChildAdded(initialArray, toValue(child), toKey, prevKey); - } - - // only emit the array after the initial load - if (hasInitialLoad) { - obs.next(initialArray); - } - }, err => { - if (err) { obs.error(err); obs.complete(); } - }); + // Keep track of callback handles for calling ref.off(event, handle) + const handles = []; + let hasLoaded = false; + let lastLoadedKey: string = null; + let array = []; - handles.push({ event: 'child_added', handle: addFn }); + // The list children are always added to, removed from and changed within + // the array using the child_added/removed/changed events. The value event + // is only used to determine when the initial load is complete. - let remFn = ref.on('child_removed', (child: any) => { - initialArray = onChildRemoved(initialArray, toValue(child), toKey); - if (hasInitialLoad) { - obs.next(initialArray); - } - }, err => { - if (err) { obs.error(err); obs.complete(); } - }); - handles.push({ event: 'child_removed', handle: remFn }); - - let chgFn = ref.on('child_changed', (child: any, prevKey: string) => { - initialArray = onChildChanged(initialArray, toValue(child), toKey, prevKey) - if (hasInitialLoad) { - // This also manages when the only change is prevKey change - obs.next(initialArray); - } - }, err => { - if (err) { obs.error(err); obs.complete(); } + ref.once('value', (snap: any) => { + if (snap.exists()) { + snap.forEach((child: any) => { + lastLoadedKey = child.key; }); - handles.push({ event: 'child_changed', handle: chgFn }); - - // If empty emit the array - if (isInitiallyEmpty) { - obs.next(initialArray); - hasInitialLoad = true; + if (array.find((child: any) => toKey(child) === lastLoadedKey)) { + hasLoaded = true; + obs.next(array); } - }, err => { - if (err) { - obs.error(err); - obs.complete(); - } - }); + } else { + hasLoaded = true; + obs.next(array); + } + }, err => { + if (err) { obs.error(err); obs.complete(); } + }); + + const addFn = ref.on('child_added', (child: any, prevKey: string) => { + array = onChildAdded(array, toValue(child), toKey, prevKey); + if (hasLoaded) { + obs.next(array); + } else if (child.key === lastLoadedKey) { + hasLoaded = true; + obs.next(array); + } + }, err => { + if (err) { obs.error(err); obs.complete(); } + }); + handles.push({ event: 'child_added', handle: addFn }); + + let remFn = ref.on('child_removed', (child: any) => { + array = onChildRemoved(array, toValue(child), toKey); + if (hasLoaded) { + obs.next(array); + } + }, err => { + if (err) { obs.error(err); obs.complete(); } + }); + handles.push({ event: 'child_removed', handle: remFn }); + + let chgFn = ref.on('child_changed', (child: any, prevKey: string) => { + array = onChildChanged(array, toValue(child), toKey, prevKey); + if (hasLoaded) { + obs.next(array); + } + }, err => { + if (err) { obs.error(err); obs.complete(); } + }); + handles.push({ event: 'child_changed', handle: chgFn }); return () => { // Loop through callback handles and dispose of each event with handle