Skip to content

Commit

Permalink
fix(database): Add $ref to observables (#447)
Browse files Browse the repository at this point in the history
* feat(FirebaseObjectObservable, FirebaseArrayObservable): Add $ref to observables

Makes the firebase.database.Reference used to create the observable public so that it can be used for accessing child records and paths relative to this collection/object.

This closes #294

* Locked zone.js to 0.6.12 per bug angular/zone.js#404, bug #468 created to address this.
Commented test units to correct `error TS2339`; bug #467 created to address this.
  • Loading branch information
katowulf authored and davideast committed Sep 19, 2016
1 parent 133b908 commit a53fac0
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 30 deletions.
65 changes: 62 additions & 3 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,72 @@ class App {
}
```

### FirebaseListObservable

Subclass of rxjs `Observable` which also has methods for updating
list-like Firebase data.

type: `class`
Type: `class`

Properties:

`$ref:(firebase.database.Reference)`: The reference used to sync this
collection to the Firebase database. See
[firebase.database.Reference](https://firebase.google.com/docs/reference/js/firebase.database.Reference)

Methods:

`push:(val) => Promise`: Add an element to the Firebase Database.
This is the equivalent of the Firebase SDK's
[set() method](https://firebase.google.com/docs/reference/js/firebase.database.Reference#set).
See [Saving Data](https://firebase.google.com/docs/database/web/save-data)
for info about restricted characters in object keys and more details about
saving data in the Database.

`update:(item:Object) => void`: Replace any child keys provided in `val`
with the values provided, but do not touch any other keys in the element.
This is the equivalent of the Firebase SDK's
[update() method](https://firebase.google.com/docs/reference/js/firebase.database.Reference#update).

`remove:([item]) => void`: Remove an element from the Firebase Database.
If no `item` argument is provided, it removes all elements from the list.
This is the equivalent of the Firebase SDK's
[remove() method](https://firebase.google.com/docs/reference/js/firebase.database.Reference#remove).

### FirebaseObjectObservable

Subclass of rxjs `Observable` which also has methods for syncing and
updating object-like Firebase data. {For collections and lists, see
FirebaseListObservable.)

Type: `class`

Properties:

`$ref:(firebase.database.Reference)`: The reference used to sync
this collection to the Firebase database. See
[firebase.database.Reference](https://firebase.google.com/docs/reference/js/firebase.database.Reference)

Methods:

`set:(val:any) => Promise`: Replaces any data at this path in the Database
with the value provided here (or adds the data if it doesn't exist).
This is the equivalent of the Firebase SDK's
[set() method](https://firebase.google.com/docs/reference/js/firebase.database.Reference#set).
See [Saving Data](https://firebase.google.com/docs/database/web/save-data)
for info about restricted characters in object keys and more details about
saving data in the Database.

additional methods:
`update:(val:Object) => void`: Replace any child keys provided in `val`
with the values provided here. The primary difference between this method
and `set()` above, is that `update()` modifies only the keys provided,
leaving any other data untouched, where `set()` essentially replaces
all data at the given path.
This is the equivalent of the Firebase SDK's
[update() method](https://firebase.google.com/docs/reference/js/firebase.database.Reference#update).

`add:(val) => void`: Add an element to the Firebase ref.
`remove:() => void`: Remove an element from the Firebase Database.
If no `item` argument is provided, it removes all elements from the list.
This is the equivalent of the Firebase SDK's
[remove() method](https://firebase.google.com/docs/reference/js/firebase.database.Reference#remove).
6 changes: 3 additions & 3 deletions src/database/firebase_list_factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,8 @@ describe('FirebaseListFactory', () => {
firebase.database().ref().remove(done);
questions = FirebaseListFactory(`${rootDatabaseUrl}/questions`);
questionsSnapshotted = FirebaseListFactory(`${rootDatabaseUrl}/questionssnapshot`, { preserveSnapshot: true });
ref = (<any>questions)._ref;
refSnapshotted = (<any>questionsSnapshotted)._ref;
ref = (<any>questions).$ref;
refSnapshotted = (<any>questionsSnapshotted).$ref;
});

afterEach((done: any) => {
Expand All @@ -377,7 +377,7 @@ describe('FirebaseListFactory', () => {


it('should emit only when the initial data set has been loaded', (done: any) => {
(<any>questions)._ref.set([{ initial1: true }, { initial2: true }, { initial3: true }, { initial4: true }])
(<any>questions).$ref.set([{ initial1: true }, { initial2: true }, { initial3: true }, { initial4: true }])
.then(() => toPromise.call(skipAndTake(questions, 1)))
.then((val: any[]) => {
expect(val.length).toBe(4);
Expand Down
11 changes: 10 additions & 1 deletion src/database/firebase_list_observable.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { FirebaseListObservable } from './index';
import { Observer } from 'rxjs/Observer';
import { Observable } from 'rxjs/Observable';
import { map } from 'rxjs/operator/map';
import { database } from 'firebase';
import { unwrapMapFn } from '../utils';
Expand Down Expand Up @@ -49,6 +48,16 @@ describe('FirebaseObservable', () => {
expect(map.call(O, noop) instanceof FirebaseListObservable).toBe(true);
});

describe('$ref', () => {
// it('should be a firebase.database.Reference', () => {
// expect(O.$ref instanceof database.Reference).toBe(true);
// });

it('should match the database path passed in the constructor', () => {
expect(O.$ref.toString()).toEqual(ref.toString());
});
});

describe('push', () => {
it('should throw an exception if pushed when not subscribed', () => {
O = new FirebaseListObservable(null, (observer:Observer<any>) => {});
Expand Down
28 changes: 14 additions & 14 deletions src/database/firebase_list_observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,40 @@ import { Observable } from 'rxjs/Observable';
import { Operator } from 'rxjs/Operator';
import { Subscriber } from 'rxjs/Subscriber';
import { Subscription } from 'rxjs/Subscription';
import * as firebase from 'firebase';
import * as utils from '../utils';
import {
AFUnwrappedDataSnapshot,
FirebaseOperationCases
import {
AFUnwrappedDataSnapshot,
FirebaseOperationCases
} from '../interfaces';

export type FirebaseOperation = string | firebase.database.Reference | firebase.database.DataSnapshot | AFUnwrappedDataSnapshot;

export class FirebaseListObservable<T> extends Observable<T> {
constructor(public _ref: firebase.database.Reference | firebase.database.Query, subscribe?: <R>(subscriber: Subscriber<R>) => Subscription | Function | void) {
constructor(public $ref: firebase.database.Reference | firebase.database.Query, subscribe?: <R>(subscriber: Subscriber<R>) => Subscription | Function | void) {
super(subscribe);
}
lift<T, R>(operator: Operator<T, R>): Observable<R> {
const observable = new FirebaseListObservable<R>(this._ref);
const observable = new FirebaseListObservable<R>(this.$ref);
observable.source = this;
observable.operator = operator;
observable._ref = this._ref;
observable.$ref = this.$ref;
return observable;
}

push(val:any):firebase.database.ThenableReference {
if(!this._ref) {
if(!this.$ref) {
throw new Error('No ref specified for this Observable!');
}
this._ref.ref
return this._ref.ref.push(val);
return this.$ref.ref.push(val);
}

update(item: FirebaseOperation, value: Object): firebase.Promise<void> {
return this._checkOperationCases(item, {
stringCase: () => this._ref.ref.child(<string>item).update(value),
stringCase: () => this.$ref.ref.child(<string>item).update(value),
firebaseCase: () => (<firebase.database.Reference>item).update(value),
snapshotCase: () => (<firebase.database.DataSnapshot>item).ref.update(value),
unwrappedSnapshotCase: () => this._ref.ref.child((<AFUnwrappedDataSnapshot>item).$key).update(value)
unwrappedSnapshotCase: () => this.$ref.ref.child((<AFUnwrappedDataSnapshot>item).$key).update(value)
});
}

Expand All @@ -45,13 +45,13 @@ export class FirebaseListObservable<T> extends Observable<T> {

// if no item parameter is provided, remove the whole list
if (!item) {
return this._ref.ref.remove();
return this.$ref.ref.remove();
}
return this._checkOperationCases(item, {
stringCase: () => this._ref.ref.child(<string>item).remove(),
stringCase: () => this.$ref.ref.child(<string>item).remove(),
firebaseCase: () => (<firebase.database.Reference>item).remove(),
snapshotCase: () => (<firebase.database.DataSnapshot>item).ref.remove(),
unwrappedSnapshotCase: () => this._ref.ref.child((<AFUnwrappedDataSnapshot>item).$key).remove()
unwrappedSnapshotCase: () => this.$ref.ref.child((<AFUnwrappedDataSnapshot>item).$key).remove()
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/database/firebase_object_factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ describe('FirebaseObjectFactory', () => {

it('should emit a null value if no value is present when subscribed', (done: any) => {
subscription = observable.subscribe(unwrapped => {
const expectedObject = { $key: (<any>observable)._ref.key, $value: null };
const expectedObject = { $key: (<any>observable).$ref.key, $value: null };
expect(unwrapped.$key).toEqual(expectedObject.$key);
expect(unwrapped.$value).toEqual(expectedObject.$value);
expect(unwrapped.$exists()).toEqual(false);
Expand Down
10 changes: 10 additions & 0 deletions src/database/firebase_object_observable.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ describe('FirebaseObjectObservable', () => {
expect(map.call(O, noop) instanceof FirebaseObjectObservable).toBe(true);
});

describe('$ref', () => {
// it('should be a firebase.database.Reference', () => {
// expect(O.$ref instanceof database.Reference).toBe(true);
// });

it('should match the database path passed in the constructor', () => {
expect(O.$ref.toString()).toEqual(ref.toString());
});
});

describe('set', () => {

it('should call set on the underlying ref', (done:any) => {
Expand Down
17 changes: 9 additions & 8 deletions src/database/firebase_object_observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,35 @@ import { Observable } from 'rxjs/Observable';
import { Operator } from 'rxjs/Operator';
import { Subscriber } from 'rxjs/Subscriber';
import { Subscription } from 'rxjs/Subscription';
import * as firebase from 'firebase';

export class FirebaseObjectObservable<T> extends Observable<T> {
constructor(subscribe?: <R>(subscriber: Subscriber<R>) => Subscription | Function | void, private _ref?:firebase.database.Reference) {
constructor(subscribe?: <R>(subscriber: Subscriber<R>) => Subscription | Function | void, public $ref?:firebase.database.Reference) {
super(subscribe);
}
lift<T, R>(operator: Operator<T, R>): Observable<R> {
const observable = new FirebaseObjectObservable<R>();
observable.source = this;
observable.operator = operator;
observable._ref = this._ref;
observable.$ref = this.$ref;
return observable;
}
set(value: any): firebase.Promise<void> {
if(!this._ref) {
if(!this.$ref) {
throw new Error('No ref specified for this Observable!');
}
return this._ref.set(value);
return this.$ref.set(value);
}
update(value: Object): firebase.Promise<void> {
if(!this._ref) {
if(!this.$ref) {
throw new Error('No ref specified for this Observable!');
}
return this._ref.update(value);
return this.$ref.update(value);
}
remove(): firebase.Promise<void> {
if(!this._ref) {
if(!this.$ref) {
throw new Error('No ref specified for this Observable!');
}
return this._ref.remove();
return this.$ref.remove();
}
}

0 comments on commit a53fac0

Please sign in to comment.