Skip to content

Commit 1fd4779

Browse files
committed
refactor(db): use action model
1 parent 6245f47 commit 1fd4779

14 files changed

+131
-101
lines changed

src/database/interfaces.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,18 @@ export type SnapshotChange = {
3939
prevKey: string | undefined;
4040
}
4141

42+
export type Action<T> = {
43+
type: string;
44+
payload: T;
45+
};
46+
4247
export interface SnapshotPrevKey {
4348
snapshot: DatabaseSnapshot | null;
4449
prevKey: string | undefined;
4550
}
4651

52+
export type SnapshotAction = Action<SnapshotPrevKey>;
53+
4754
export type Primitive = number | string | boolean;
4855

4956
export type DatabaseSnapshot = firebase.database.DataSnapshot;

src/database/list/changes.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ describe('listChanges', () => {
4848
someRef.set(batch);
4949
const obs = listChanges(someRef, ['child_added']);
5050
const sub = obs.skip(2).subscribe(changes => {
51-
const data = changes.map(change => change.snapshot!.val());
51+
const data = changes.map(change => change.payload.snapshot!.val());
5252
expect(data).toEqual(items);
5353
done();
5454
});
@@ -59,7 +59,7 @@ describe('listChanges', () => {
5959
aref.set(batch);
6060
const obs = listChanges(aref, ['child_added']);
6161
const sub = obs.skip(3).subscribe(changes => {
62-
const data = changes.map(change => change.snapshot!.val());
62+
const data = changes.map(change => change.payload.snapshot!.val());
6363
expect(data[3]).toEqual({ name: 'anotha one' });
6464
done();
6565
});
@@ -72,7 +72,7 @@ describe('listChanges', () => {
7272
const obs = listChanges(aref, ['child_added','child_removed'])
7373

7474
const sub = obs.skip(3).subscribe(changes => {
75-
const data = changes.map(change => change.snapshot!.val());
75+
const data = changes.map(change => change.payload.snapshot!.val());
7676
expect(data.length).toEqual(items.length - 1);
7777
done();
7878
});
@@ -84,9 +84,9 @@ describe('listChanges', () => {
8484
const aref = ref(rando());
8585
aref.set(batch);
8686
const obs = listChanges(aref, ['child_added','child_changed'])
87-
87+
debugger;
8888
const sub = obs.skip(3).subscribe(changes => {
89-
const data = changes.map(change => change.snapshot!.val());
89+
const data = changes.map(change => change.payload.snapshot!.val());
9090
expect(data[0].name).toEqual('lol');
9191
done();
9292
});
@@ -99,7 +99,7 @@ describe('listChanges', () => {
9999
aref.set(batch);
100100
const obs = listChanges(aref, ['child_added','child_moved'])
101101
const sub = obs.skip(3).subscribe(changes => {
102-
const data = changes.map(change => change.snapshot!.val());
102+
const data = changes.map(change => change.payload.snapshot!.val());
103103
// We moved the first item to the last item, so we check that
104104
// the new result is now the last result
105105
expect(data[data.length - 1]).toEqual(items[0]);

src/database/list/changes.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
import { fromRef } from '../observable/fromRef';
22
import { Observable } from 'rxjs/Observable';
3-
import { DatabaseQuery, ChildEvent, SnapshotChange} from '../interfaces';
3+
import { DatabaseQuery, ChildEvent, SnapshotChange, Action} from '../interfaces';
44
import { positionFor, positionAfter } from './utils';
55
import 'rxjs/add/operator/scan';
66
import 'rxjs/add/observable/merge';
77

88
// TODO(davideast): check safety of ! operator in scan
9-
export function listChanges<T>(ref: DatabaseQuery, events: ChildEvent[]): Observable<SnapshotChange[]> {
9+
export function listChanges<T>(ref: DatabaseQuery, events: ChildEvent[]): Observable<Action<any>[]> {
1010
const childEvent$ = events.map(event => fromRef(ref, event));
1111
return Observable.merge(...childEvent$)
12-
.scan((current, change) => {
13-
const { snapshot, event } = change;
14-
switch (change.event) {
12+
.scan((current, action) => {
13+
const { payload, type } = action;
14+
switch (action.type) {
1515
case 'child_added':
16-
return [...current, change];
16+
return [...current, action];
1717
case 'child_removed':
1818
// ! is okay here because only value events produce null results
19-
return current.filter(x => x.snapshot!.key !== snapshot!.key);
19+
return current.filter(x => x.payload.snapshot!.key !== payload.snapshot!.key);
2020
case 'child_changed':
21-
return current.map(x => x.snapshot!.key === change.snapshot!.key ? change : x);
21+
return current.map(x => x.payload.snapshot!.key === payload.snapshot!.key ? action : x);
2222
// default will also remove null results
2323
case 'child_moved':
24-
const curPos = positionFor(current, change.snapshot!.key)
24+
const curPos = positionFor(current, payload.snapshot!.key)
2525
if(curPos > -1) {
2626
const data = current.splice(curPos, 1)[0];
27-
const newPost = positionAfter(current, change.prevKey!);
27+
const newPost = positionAfter(current, payload.prevKey!);
2828
current.splice(newPost, 0, data);
2929
return current;
3030
}

src/database/list/create-reference.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
import { DatabaseQuery, ListReference } from '../interfaces';
2-
import { createListValueChanges } from './value-changes';
3-
import { createLoadedChanges } from './loaded';
1+
import { DatabaseQuery, ListReference, ChildEvent } from '../interfaces';
2+
import { createLoadedChanges, loadedSnapshotChanges } from './loaded';
43
import { createDataOperationMethod } from './data-operation';
54
import { createRemoveMethod } from './remove';
65

76
export function createListReference<T>(query: DatabaseQuery): ListReference<T> {
87
return {
98
query,
10-
snapshotChanges: createLoadedChanges(query),
11-
valueChanges<T>() { return createLoadedChanges(query)().map(snaps => snaps.map(snap => snap!.val())); },
129
update: createDataOperationMethod<T>(query.ref, 'update'),
1310
set: createDataOperationMethod<T>(query.ref, 'set'),
1411
push: (data: T) => query.ref.push(data),
15-
remove: createRemoveMethod(query.ref)
12+
remove: createRemoveMethod(query.ref),
13+
snapshotChanges: createLoadedChanges(query),
14+
valueChanges<T>(events?: ChildEvent[]) {
15+
return loadedSnapshotChanges(query, events)
16+
.map(snaps => snaps.map(snap => snap!.val()));
17+
}
1618
}
1719
}

src/database/list/loaded.ts

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { DatabaseQuery, ChildEvent, DatabaseSnapshot } from '../interfaces';
1+
import { DatabaseQuery, ChildEvent, DatabaseSnapshot, SnapshotPrevKey } from '../interfaces';
22
import { fromRef } from '../observable/fromRef';
3-
import { createListSnapshotChanges } from './snapshot-changes';
3+
import { snapshotChanges } from './snapshot-changes';
44
import { Observable } from 'rxjs/Observable';
55
import 'rxjs/add/operator/skipWhile';
66
import 'rxjs/add/operator/withLatestFrom';
7-
import 'rxjs/add/operator/filter';
87
import { database } from 'firebase/app';
98

109
/**
@@ -17,39 +16,45 @@ import { database } from 'firebase/app';
1716
* @param query
1817
*/
1918
export function createLoadedChanges(query: DatabaseQuery) {
19+
return (events?: ChildEvent[]) => loadedSnapshotChanges(query, events);
20+
}
21+
22+
export function createLoadedList(query: DatabaseQuery) {
2023
// Create an observable of loaded values to retrieve the
2124
// known dataset. This will allow us to know what key to
2225
// emit the "whole" array at when listening for child events.
23-
const loaded$ = fromRef(query, 'value')
24-
.map(data => {
25-
// Store the last key in the data set
26-
let lastKeyToLoad;
27-
// Loop through loaded dataset to find the last key
28-
data.snapshot!.forEach(child => {
29-
lastKeyToLoad = child.key; return false;
30-
});
31-
// return data set
32-
return { data, lastKeyToLoad };
26+
return fromRef(query, 'value')
27+
.map(data => {
28+
// Store the last key in the data set
29+
let lastKeyToLoad;
30+
// Loop through loaded dataset to find the last key
31+
data.payload.snapshot!.forEach(child => {
32+
lastKeyToLoad = child.key; return false;
3333
});
34-
return function snapshotChanges(events?: ChildEvent[]) {
35-
const snapChanges$ = createListSnapshotChanges(query)(events);
36-
return loaded$
37-
.withLatestFrom(snapChanges$)
38-
// Get the latest values from the "loaded" and "child" datasets
39-
// This way
40-
.map(([loaded, snaps]) => {
41-
// Store the last key in the data set
42-
let lastKeyToLoad = loaded.lastKeyToLoad;
43-
// Store all child keys loaded at this point
44-
const loadedKeys = snaps.map(snap => snap.key);
45-
return { snaps, lastKeyToLoad, loadedKeys }
46-
})
47-
// This is the magical part, only emit when the last load key
48-
// in the dataset has been loaded by a child event. At this point
49-
// we can assume the dataset is "whole".
50-
.skipWhile(meta => meta.loadedKeys.indexOf(meta.lastKeyToLoad) === -1)
51-
// Pluck off the meta data because the user only cares
52-
// to iterate through the snapshots
53-
.map(meta => meta.snaps);
54-
}
34+
// return data set and the current last key loaded
35+
return { data, lastKeyToLoad };
36+
});
37+
}
38+
39+
export function loadedSnapshotChanges(query: DatabaseQuery, events?: ChildEvent[]) {
40+
const snapChanges$ = snapshotChanges(query, events);
41+
const loaded$ = createLoadedList(query);
42+
return loaded$
43+
.withLatestFrom(snapChanges$)
44+
// Get the latest values from the "loaded" and "child" datasets
45+
// We can use both datasets to form an array of the latest values.
46+
.map(([loaded, snaps]) => {
47+
// Store the last key in the data set
48+
let lastKeyToLoad = loaded.lastKeyToLoad;
49+
// Store all child keys loaded at this point
50+
const loadedKeys = snaps.map(snap => snap.key);
51+
return { snaps, lastKeyToLoad, loadedKeys }
52+
})
53+
// This is the magical part, only emit when the last load key
54+
// in the dataset has been loaded by a child event. At this point
55+
// we can assume the dataset is "whole".
56+
.skipWhile(meta => meta.loadedKeys.indexOf(meta.lastKeyToLoad) === -1)
57+
// Pluck off the meta data because the user only cares
58+
// to iterate through the snapshots
59+
.map(meta => meta.snaps);
5560
}

src/database/list/snapshot-changes.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as firebase from 'firebase/app';
22
import { FirebaseApp, FirebaseAppConfig, AngularFireModule } from 'angularfire2';
3-
import { AngularFireDatabase, AngularFireDatabaseModule, createListSnapshotChanges, ChildEvent } from 'angularfire2/database';
3+
import { AngularFireDatabase, AngularFireDatabaseModule, snapshotChanges, ChildEvent } from 'angularfire2/database';
44
import { TestBed, inject } from '@angular/core/testing';
55
import { COMMON_CONFIG } from '../test-config';
66
import 'rxjs/add/operator/skip';
@@ -45,16 +45,16 @@ describe('snapshotChanges', () => {
4545
const { events, skip } = opts;
4646
const aref = createRef(rando());
4747
aref.set(batch);
48-
const snapshotChanges = createListSnapshotChanges(aref);
48+
const snapChanges = snapshotChanges(aref, events);
4949
return {
50-
snapshotChanges: snapshotChanges(events).skip(skip),
50+
snapChanges: snapChanges.skip(skip),
5151
ref: aref
5252
};
5353
}
5454

5555
it('should listen to all events by default', (done) => {
56-
const { snapshotChanges } = prepareSnapshotChanges({ skip: 2 });
57-
const sub = snapshotChanges.subscribe(snaps => {
56+
const { snapChanges } = prepareSnapshotChanges({ skip: 2 });
57+
const sub = snapChanges.subscribe(snaps => {
5858
const data = snaps.map(snap => snap.val())
5959
expect(data).toEqual(items);
6060
done();
@@ -63,8 +63,8 @@ describe('snapshotChanges', () => {
6363
});
6464

6565
it('should listen to only child_added events', (done) => {
66-
const { snapshotChanges } = prepareSnapshotChanges({ events: ['child_added'], skip: 2 });
67-
const sub = snapshotChanges.subscribe(snaps => {
66+
const { snapChanges } = prepareSnapshotChanges({ events: ['child_added'], skip: 2 });
67+
const sub = snapChanges.subscribe(snaps => {
6868
const data = snaps.map(snap => snap.val())
6969
expect(data).toEqual(items);
7070
done();
@@ -73,12 +73,12 @@ describe('snapshotChanges', () => {
7373
});
7474

7575
it('should listen to only child_added, child_changed events', (done) => {
76-
const {snapshotChanges, ref } = prepareSnapshotChanges({
76+
const { snapChanges, ref } = prepareSnapshotChanges({
7777
events: ['child_added', 'child_changed'],
7878
skip: 3
7979
});
8080
const name = 'ligatures';
81-
const sub = snapshotChanges.subscribe(snaps => {
81+
const sub = snapChanges.subscribe(snaps => {
8282
const data = snaps.map(snap => snap.val());
8383
const copy = [...items];
8484
copy[0].name = name;

src/database/list/snapshot-changes.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@ import { validateEventsArray } from './utils';
66
import 'rxjs/add/operator/map';
77

88
// TODO(davideast): Test safety of ! unwrap
9-
export function createListSnapshotChanges(query: DatabaseQuery) {
10-
return function snapshotChanges(events?: ChildEvent[]): Observable<DatabaseSnapshot[]> {
11-
events = validateEventsArray(events);
12-
return listChanges(query, events!)
13-
.map(changes => changes.map(change => change.snapshot!));
14-
}
9+
export function snapshotChanges(query: DatabaseQuery, events?: ChildEvent[]): Observable<DatabaseSnapshot[]> {
10+
events = validateEventsArray(events);
11+
return listChanges(query, events!)
12+
.map(changes => changes.map(change => change.payload.snapshot!));
1513
}

src/database/list/state-changes.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { DatabaseQuery, ChildEvent, SnapshotChange, SnapshotPrevKey } from '../interfaces';
2+
import { fromRef } from '../observable/fromRef';
3+
import { validateEventsArray } from './utils';
4+
import { Observable } from 'rxjs/Observable';
5+
import 'rxjs/add/observable/merge';
6+
import 'rxjs/add/operator/scan';
7+
8+
export function createStateChanges(query: DatabaseQuery) {
9+
return (events?: ChildEvent[]) => stateChanges(query, events);
10+
}
11+
12+
export function stateChanges(query: DatabaseQuery, events?: ChildEvent[]) {
13+
events = validateEventsArray(events)!;
14+
const childEvent$ = events.map(event => fromRef(query, event));
15+
return Observable
16+
.merge(...childEvent$)
17+
.scan((current, change) => [...current, change], []);
18+
}

src/database/list/utils.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isNil } from '../utils';
2+
import { SnapshotAction } from '../interfaces';
23

34
export function validateEventsArray(events?: any[]) {
45
if(isNil(events) || events!.length === 0) {
@@ -7,17 +8,17 @@ export function validateEventsArray(events?: any[]) {
78
return events;
89
}
910

10-
export function positionFor(changes, key) {
11+
export function positionFor(changes: SnapshotAction[], key) {
1112
const len = changes.length;
1213
for(let i=0; i<len; i++) {
13-
if(changes[i].snapshot.key === key) {
14+
if(changes[i].payload.snapshot!.key === key) {
1415
return i;
1516
}
1617
}
1718
return -1;
1819
}
1920

20-
export function positionAfter(changes, prevKey: string) {
21+
export function positionAfter(changes: SnapshotAction[], prevKey: string) {
2122
if(prevKey === null) {
2223
return 0;
2324
} else {

src/database/list/value-changes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ export function createListValueChanges<T>(query: DatabaseQuery) {
88
return function valueChanges<T>(events?: ChildEvent[]): Observable<T[]> {
99
events = validateEventsArray(events);
1010
return listChanges<T>(query, events!)
11-
.map(changes => changes.map(change => change.snapshot!.val()))
11+
.map(changes => changes.map(change => change.payload.snapshot!.val()))
1212
}
1313
}

0 commit comments

Comments
 (0)