Skip to content
This repository was archived by the owner on Mar 17, 2025. It is now read-only.

Commit 0408ccc

Browse files
author
Jacob Wenger
committed
Merge pull request #556 from firebase/kato-removefb
Fixes #501 - remove $firebase object
2 parents a0674e6 + 81bcfcd commit 0408ccc

File tree

15 files changed

+790
-1588
lines changed

15 files changed

+790
-1588
lines changed

bower.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@
3535
},
3636
"devDependencies": {
3737
"angular-mocks": "~1.3.11",
38-
"mockfirebase": "~0.8.0"
38+
"mockfirebase": "~0.10.1"
3939
}
4040
}

src/FirebaseArray.js

Lines changed: 169 additions & 48 deletions
Large diffs are not rendered by default.

src/FirebaseObject.js

Lines changed: 99 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,65 @@
11
(function() {
22
'use strict';
33
/**
4-
* Creates and maintains a synchronized object. This constructor should not be
5-
* manually invoked. Instead, one should create a $firebase object and call $asObject
6-
* on it: <code>$firebase( firebaseRef ).$asObject()</code>;
4+
* Creates and maintains a synchronized object, with 2-way bindings between Angular and Firebase.
75
*
8-
* Internally, the $firebase object depends on this class to provide 2 methods, which it invokes
9-
* to notify the object whenever a change has been made at the server:
6+
* Implementations of this class are contracted to provide the following internal methods,
7+
* which are used by the synchronization process and 3-way bindings:
108
* $$updated - called whenever a change occurs (a value event from Firebase)
119
* $$error - called when listeners are canceled due to a security error
10+
* $$notify - called to update $watch listeners and trigger updates to 3-way bindings
11+
* $ref - called to obtain the underlying Firebase reference
1212
*
13-
* Instead of directly modifying this class, one should generally use the $extendFactory
13+
* Instead of directly modifying this class, one should generally use the $extend
1414
* method to add or change how methods behave:
1515
*
1616
* <pre><code>
17-
* var NewFactory = $FirebaseObject.$extendFactory({
17+
* var ExtendedObject = $FirebaseObject.$extend({
1818
* // add a new method to the prototype
1919
* foo: function() { return 'bar'; },
2020
* });
21-
* </code></pre>
2221
*
23-
* And then the new factory can be used by passing it as an argument:
24-
* <code>$firebase( firebaseRef, {objectFactory: NewFactory}).$asObject();</code>
22+
* var obj = new ExtendedObject(ref);
23+
* </code></pre>
2524
*/
2625
angular.module('firebase').factory('$FirebaseObject', [
27-
'$parse', '$firebaseUtils', '$log', '$interval',
26+
'$parse', '$firebaseUtils', '$log',
2827
function($parse, $firebaseUtils, $log) {
2928
/**
30-
* This constructor should probably never be called manually. It is used internally by
31-
* <code>$firebase.$asObject()</code>.
29+
* Creates a synchronized object with 2-way bindings between Angular and Firebase.
3230
*
33-
* @param $firebase
34-
* @param {Function} destroyFn invoking this will cancel all event listeners and stop
35-
* notifications from being delivered to $$updated and $$error
36-
* @param readyPromise resolved when the initial data downloaded from Firebase
31+
* @param {Firebase} ref
3732
* @returns {FirebaseObject}
3833
* @constructor
3934
*/
40-
function FirebaseObject($firebase, destroyFn, readyPromise) {
35+
function FirebaseObject(ref) {
4136
// These are private config props and functions used internally
4237
// they are collected here to reduce clutter in console.log and forEach
4338
this.$$conf = {
44-
promise: readyPromise,
45-
inst: $firebase,
39+
// synchronizes data to Firebase
40+
sync: new ObjectSyncManager(this, ref),
41+
// stores the Firebase ref
42+
ref: ref,
43+
// synchronizes $scope variables with this object
4644
binding: new ThreeWayBinding(this),
47-
destroyFn: destroyFn,
45+
// stores observers registered with $watch
4846
listeners: []
4947
};
5048

5149
// this bit of magic makes $$conf non-enumerable and non-configurable
5250
// and non-writable (its properties are still writable but the ref cannot be replaced)
53-
// we declare it above so the IDE can relax
51+
// we redundantly assign it above so the IDE can relax
5452
Object.defineProperty(this, '$$conf', {
5553
value: this.$$conf
5654
});
5755

58-
this.$id = $firebaseUtils.getKey($firebase.$ref().ref());
56+
this.$id = $firebaseUtils.getKey(ref.ref());
5957
this.$priority = null;
6058

6159
$firebaseUtils.applyDefaults(this, this.$$defaults);
60+
61+
// start synchronizing data with Firebase
62+
this.$$conf.sync.init();
6263
}
6364

6465
FirebaseObject.prototype = {
@@ -68,11 +69,12 @@
6869
*/
6970
$save: function () {
7071
var self = this;
71-
return self.$inst().$set($firebaseUtils.toJSON(self))
72-
.then(function(ref) {
73-
self.$$notify();
74-
return ref;
75-
});
72+
var ref = self.$ref();
73+
var data = $firebaseUtils.toJSON(self);
74+
return $firebaseUtils.doSet(ref, data).then(function() {
75+
self.$$notify();
76+
return self.$ref();
77+
});
7678
},
7779

7880
/**
@@ -83,11 +85,11 @@
8385
*/
8486
$remove: function() {
8587
var self = this;
86-
$firebaseUtils.trimKeys(this, {});
87-
this.$value = null;
88-
return self.$inst().$remove().then(function(ref) {
88+
$firebaseUtils.trimKeys(self, {});
89+
self.$value = null;
90+
return $firebaseUtils.doRemove(self.$ref()).then(function() {
8991
self.$$notify();
90-
return ref;
92+
return self.$ref();
9193
});
9294
},
9395

@@ -104,7 +106,7 @@
104106
* @returns a promise which resolves after initial data is downloaded from Firebase
105107
*/
106108
$loaded: function(resolve, reject) {
107-
var promise = this.$$conf.promise;
109+
var promise = this.$$conf.sync.ready();
108110
if (arguments.length) {
109111
// allow this method to be called just like .then
110112
// by passing any arguments on to .then
@@ -114,10 +116,10 @@
114116
},
115117

116118
/**
117-
* @returns the original $firebase object used to create this object.
119+
* @returns {Firebase} the original Firebase instance used to create this object.
118120
*/
119-
$inst: function () {
120-
return this.$$conf.inst;
121+
$ref: function () {
122+
return this.$$conf.ref;
121123
},
122124

123125
/**
@@ -172,15 +174,15 @@
172174
* Informs $firebase to stop sending events and clears memory being used
173175
* by this object (delete's its local content).
174176
*/
175-
$destroy: function (err) {
177+
$destroy: function(err) {
176178
var self = this;
177179
if (!self.$isDestroyed) {
178180
self.$isDestroyed = true;
181+
self.$$conf.sync.destroy(err);
179182
self.$$conf.binding.destroy();
180183
$firebaseUtils.each(self, function (v, k) {
181184
delete self[k];
182185
});
183-
self.$$conf.destroyFn(err);
184186
}
185187
},
186188

@@ -223,7 +225,9 @@
223225
$$scopeUpdated: function(newData) {
224226
// we use a one-directional loop to avoid feedback with 3-way bindings
225227
// since set() is applied locally anyway, this is still performant
226-
return this.$inst().$set($firebaseUtils.toJSON(newData));
228+
var def = $firebaseUtils.defer();
229+
this.$ref().set($firebaseUtils.toJSON(newData), $firebaseUtils.makeNodeResolver(def));
230+
return def.promise;
227231
},
228232

229233
/**
@@ -262,7 +266,7 @@
262266
* `objectFactory` parameter:
263267
*
264268
* <pre><code>
265-
* var MyFactory = $FirebaseObject.$extendFactory({
269+
* var MyFactory = $FirebaseObject.$extend({
266270
* // add a method onto the prototype that prints a greeting
267271
* getGreeting: function() {
268272
* return 'Hello ' + this.first_name + ' ' + this.last_name + '!';
@@ -277,7 +281,7 @@
277281
* @param {Object} [methods] a list of functions to add onto the prototype
278282
* @returns {Function} a new factory suitable for use with $firebase
279283
*/
280-
FirebaseObject.$extendFactory = function(ChildClass, methods) {
284+
FirebaseObject.$extend = function(ChildClass, methods) {
281285
if( arguments.length === 1 && angular.isObject(ChildClass) ) {
282286
methods = ChildClass;
283287
ChildClass = function() { FirebaseObject.apply(this, arguments); };
@@ -304,7 +308,7 @@
304308
if( this.scope ) {
305309
var msg = 'Cannot bind to ' + varName + ' because this instance is already bound to ' +
306310
this.key + '; one binding per instance ' +
307-
'(call unbind method or create another $firebase instance)';
311+
'(call unbind method or create another FirebaseObject instance)';
308312
$log.error(msg);
309313
return $firebaseUtils.reject(msg);
310314
}
@@ -394,6 +398,59 @@
394398
}
395399
};
396400

401+
function ObjectSyncManager(firebaseObject, ref) {
402+
function destroy(err) {
403+
if( !sync.isDestroyed ) {
404+
sync.isDestroyed = true;
405+
ref.off('value', applyUpdate);
406+
firebaseObject = null;
407+
initComplete(err||'destroyed');
408+
}
409+
}
410+
411+
function init() {
412+
ref.on('value', applyUpdate, error);
413+
ref.once('value', function(snap) {
414+
if (angular.isArray(snap.val())) {
415+
$log.warn('Storing data using array indices in Firebase can result in unexpected behavior. See https://www.firebase.com/docs/web/guide/understanding-data.html#section-arrays-in-firebase for more information. Also note that you probably wanted $FirebaseArray and not $FirebaseObject.');
416+
}
417+
418+
initComplete(null);
419+
}, initComplete);
420+
}
421+
422+
// call initComplete(); do not call this directly
423+
function _initComplete(err) {
424+
if( !isResolved ) {
425+
isResolved = true;
426+
if( err ) { def.reject(err); }
427+
else { def.resolve(firebaseObject); }
428+
}
429+
}
430+
431+
var isResolved = false;
432+
var def = $firebaseUtils.defer();
433+
var batch = $firebaseUtils.batch();
434+
var applyUpdate = batch(function(snap) {
435+
var changed = firebaseObject.$$updated(snap);
436+
if( changed ) {
437+
// notifies $watch listeners and
438+
// updates $scope if bound to a variable
439+
firebaseObject.$$notify();
440+
}
441+
});
442+
var error = batch(firebaseObject.$$error, firebaseObject);
443+
var initComplete = batch(_initComplete);
444+
445+
var sync = {
446+
isDestroyed: false,
447+
destroy: destroy,
448+
init: init,
449+
ready: function() { return def.promise; }
450+
};
451+
return sync;
452+
}
453+
397454
return FirebaseObject;
398455
}
399456
]);

0 commit comments

Comments
 (0)