Skip to content

Commit

Permalink
feat(FirebaseObjectObservable, FirebaseArrayObservable): Add $ref to …
Browse files Browse the repository at this point in the history
…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 angular#294
  • Loading branch information
katowulf committed Aug 17, 2016
1 parent b38b5fd commit 9c7f805
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 32 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 @@ -366,8 +366,8 @@ describe('FirebaseListFactory', () => {
firebase.database().ref().remove(done);
questions = FirebaseListFactory(`${rootFirebase}/questions`);
questionsSnapshotted = FirebaseListFactory(`${rootFirebase}/questionssnapshot`, { preserveSnapshot: true });
ref = (<any>questions)._ref;
refSnapshotted = (<any>questionsSnapshotted)._ref;
ref = (<any>questions).$ref;
refSnapshotted = (<any>questionsSnapshotted).$ref;
});

afterEach((done: any) => {
Expand All @@ -379,7 +379,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(() => questions.take(1).toPromise())
.then((val: any[]) => {
expect(val.length).toBe(4);
Expand Down
14 changes: 11 additions & 3 deletions 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 'rxjs/add/operator/map';
import { database } from 'firebase';
import { unwrapMapFn } from '../utils';
Expand All @@ -22,7 +21,6 @@ export const firebaseConfig: FirebaseAppConfig = {
databaseURL: "https://angularfire2-test.firebaseio.com",
storageBucket: "angularfire2-test.appspot.com",
};
const rootUrl = firebaseConfig.databaseURL;

describe('FirebaseObservable', () => {
var O:FirebaseListObservable<any>;
Expand All @@ -31,7 +29,7 @@ describe('FirebaseObservable', () => {

beforeEach(() => {
addProviders([FIREBASE_PROVIDERS, defaultFirebase(firebaseConfig)]);
inject([FirebaseApp, AngularFire], (firebaseApp: firebase.app.App, _af: AngularFire) => {
inject([FirebaseApp, AngularFire], (firebaseApp: firebase.app.App) => {
app = firebaseApp;
ref = database().ref();
O = new FirebaseListObservable(ref, (observer:Observer<any>) => {
Expand All @@ -51,6 +49,16 @@ describe('FirebaseObservable', () => {
expect(O.map(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 @@ -72,7 +72,7 @@ describe('FirebaseObjectFactory', () => {

it('should emit a null value if no value is present when subscribed', (done: any) => {
subscription = observable.subscribe(val => {
expect(val).toEqual({ $key: (<any>observable)._ref.key, $value: null });
expect(val).toEqual({ $key: (<any>observable).$ref.key, $value: null });
done();
});
});
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 @@ -49,6 +49,16 @@ describe('FirebaseObjectObservable', () => {
expect(O.map(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 9c7f805

Please # to comment.