diff --git a/README.md b/README.md
index d5a9e3d1..32fd76ca 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ In order to use AngularFire in your project, you need to include the following f
-
+
```
Use the URL above to download both the minified and non-minified versions of AngularFire from the
diff --git a/bower.json b/bower.json
index 756443c7..7d1e3a44 100644
--- a/bower.json
+++ b/bower.json
@@ -1,7 +1,7 @@
{
"name": "angularfire",
"description": "The officially supported AngularJS binding for Firebase",
- "version": "0.0.0",
+ "version": "0.9.1",
"authors": [
"Firebase (https://www.firebase.com/)"
],
diff --git a/dist/angularfire.js b/dist/angularfire.js
new file mode 100644
index 00000000..40443aa8
--- /dev/null
+++ b/dist/angularfire.js
@@ -0,0 +1,2376 @@
+/*!
+ * AngularFire is the officially supported AngularJS binding for Firebase. Firebase
+ * is a full backend so you don't need servers to build your Angular app. AngularFire
+ * provides you with the $firebase service which allows you to easily keep your $scope
+ * variables in sync with your Firebase backend.
+ *
+ * AngularFire 0.9.1
+ * https://github.com/firebase/angularfire/
+ * Date: 01/08/2015
+ * License: MIT
+ */
+(function(exports) {
+ "use strict";
+
+// Define the `firebase` module under which all AngularFire
+// services will live.
+ angular.module("firebase", [])
+ //todo use $window
+ .value("Firebase", exports.Firebase)
+
+ // used in conjunction with firebaseUtils.debounce function, this is the
+ // amount of time we will wait for additional records before triggering
+ // Angular's digest scope to dirty check and re-render DOM elements. A
+ // larger number here significantly improves performance when working with
+ // big data sets that are frequently changing in the DOM, but delays the
+ // speed at which each record is rendered in real-time. A number less than
+ // 100ms will usually be optimal.
+ .value('firebaseBatchDelay', 50 /* milliseconds */);
+
+})(window);
+(function() {
+ 'use strict';
+ /**
+ * Creates and maintains a synchronized list of data. This constructor should not be
+ * manually invoked. Instead, one should create a $firebase object and call $asArray
+ * on it: $firebase( firebaseRef ).$asArray();
+ *
+ * Internally, the $firebase object depends on this class to provide 5 $$ methods, which it invokes
+ * to notify the array whenever a change has been made at the server:
+ * $$added - called whenever a child_added event occurs
+ * $$updated - called whenever a child_changed event occurs
+ * $$moved - called whenever a child_moved event occurs
+ * $$removed - called whenever a child_removed event occurs
+ * $$error - called when listeners are canceled due to a security error
+ * $$process - called immediately after $$added/$$updated/$$moved/$$removed
+ * to splice/manipulate the array and invokes $$notify
+ *
+ * Additionally, these methods may be of interest to devs extending this class:
+ * $$notify - triggers notifications to any $watch listeners, called by $$process
+ * $$getKey - determines how to look up a record's key (returns $id by default)
+ *
+ * Instead of directly modifying this class, one should generally use the $extendFactory
+ * method to add or change how methods behave. $extendFactory modifies the prototype of
+ * the array class by returning a clone of $FirebaseArray.
+ *
+ *
+ * var NewFactory = $FirebaseArray.$extendFactory({
+ * // add a new method to the prototype
+ * foo: function() { return 'bar'; },
+ *
+ * // change how records are created
+ * $$added: function(snap, prevChild) {
+ * return new Widget(snap, prevChild);
+ * },
+ *
+ * // change how records are updated
+ * $$updated: function(snap) {
+ * return this.$getRecord(snap.key()).update(snap);
+ * }
+ * });
+ *
+ *
+ * And then the new factory can be passed as an argument:
+ * $firebase( firebaseRef, {arrayFactory: NewFactory}).$asArray();
+ */
+ angular.module('firebase').factory('$FirebaseArray', ["$log", "$firebaseUtils",
+ function($log, $firebaseUtils) {
+ /**
+ * This constructor should probably never be called manually. It is used internally by
+ * $firebase.$asArray().
+ *
+ * @param $firebase
+ * @param {Function} destroyFn invoking this will cancel all event listeners and stop
+ * notifications from being delivered to $$added, $$updated, $$moved, and $$removed
+ * @param readyPromise resolved when the initial data downloaded from Firebase
+ * @returns {Array}
+ * @constructor
+ */
+ function FirebaseArray($firebase, destroyFn, readyPromise) {
+ var self = this;
+ this._observers = [];
+ this.$list = [];
+ this._inst = $firebase;
+ this._promise = readyPromise;
+ this._destroyFn = destroyFn;
+
+ // indexCache is a weak hashmap (a lazy list) of keys to array indices,
+ // items are not guaranteed to stay up to date in this list (since the data
+ // array can be manually edited without calling the $ methods) and it should
+ // always be used with skepticism regarding whether it is accurate
+ // (see $indexFor() below for proper usage)
+ this._indexCache = {};
+
+ // Array.isArray will not work on objects which extend the Array class.
+ // So instead of extending the Array class, we just return an actual array.
+ // However, it's still possible to extend FirebaseArray and have the public methods
+ // appear on the array object. We do this by iterating the prototype and binding
+ // any method that is not prefixed with an underscore onto the final array.
+ $firebaseUtils.getPublicMethods(self, function(fn, key) {
+ self.$list[key] = fn.bind(self);
+ });
+
+ return this.$list;
+ }
+
+ FirebaseArray.prototype = {
+ /**
+ * Create a new record with a unique ID and add it to the end of the array.
+ * This should be used instead of Array.prototype.push, since those changes will not be
+ * synchronized with the server.
+ *
+ * Any value, including a primitive, can be added in this way. Note that when the record
+ * is created, the primitive value would be stored in $value (records are always objects
+ * by default).
+ *
+ * Returns a future which is resolved when the data has successfully saved to the server.
+ * The resolve callback will be passed a Firebase ref representing the new data element.
+ *
+ * @param data
+ * @returns a promise resolved after data is added
+ */
+ $add: function(data) {
+ this._assertNotDestroyed('$add');
+ return this.$inst().$push($firebaseUtils.toJSON(data));
+ },
+
+ /**
+ * Pass either an item in the array or the index of an item and it will be saved back
+ * to Firebase. While the array is read-only and its structure should not be changed,
+ * it is okay to modify properties on the objects it contains and then save those back
+ * individually.
+ *
+ * Returns a future which is resolved when the data has successfully saved to the server.
+ * The resolve callback will be passed a Firebase ref representing the saved element.
+ * If passed an invalid index or an object which is not a record in this array,
+ * the promise will be rejected.
+ *
+ * @param {int|object} indexOrItem
+ * @returns a promise resolved after data is saved
+ */
+ $save: function(indexOrItem) {
+ this._assertNotDestroyed('$save');
+ var self = this;
+ var item = self._resolveItem(indexOrItem);
+ var key = self.$keyAt(item);
+ if( key !== null ) {
+ return self.$inst().$set(key, $firebaseUtils.toJSON(item))
+ .then(function(ref) {
+ self.$$notify('child_changed', key);
+ return ref;
+ });
+ }
+ else {
+ return $firebaseUtils.reject('Invalid record; could determine its key: '+indexOrItem);
+ }
+ },
+
+ /**
+ * Pass either an existing item in this array or the index of that item and it will
+ * be removed both locally and in Firebase. This should be used in place of
+ * Array.prototype.splice for removing items out of the array, as calling splice
+ * will not update the value on the server.
+ *
+ * Returns a future which is resolved when the data has successfully removed from the
+ * server. The resolve callback will be passed a Firebase ref representing the deleted
+ * element. If passed an invalid index or an object which is not a record in this array,
+ * the promise will be rejected.
+ *
+ * @param {int|object} indexOrItem
+ * @returns a promise which resolves after data is removed
+ */
+ $remove: function(indexOrItem) {
+ this._assertNotDestroyed('$remove');
+ var key = this.$keyAt(indexOrItem);
+ if( key !== null ) {
+ return this.$inst().$remove(key);
+ }
+ else {
+ return $firebaseUtils.reject('Invalid record; could not find key: '+indexOrItem);
+ }
+ },
+
+ /**
+ * Given an item in this array or the index of an item in the array, this returns the
+ * Firebase key (record.$id) for that record. If passed an invalid key or an item which
+ * does not exist in this array, it will return null.
+ *
+ * @param {int|object} indexOrItem
+ * @returns {null|string}
+ */
+ $keyAt: function(indexOrItem) {
+ var item = this._resolveItem(indexOrItem);
+ return this.$$getKey(item);
+ },
+
+ /**
+ * The inverse of $keyAt, this method takes a Firebase key (record.$id) and returns the
+ * index in the array where that record is stored. If the record is not in the array,
+ * this method returns -1.
+ *
+ * @param {String} key
+ * @returns {int} -1 if not found
+ */
+ $indexFor: function(key) {
+ var self = this;
+ var cache = self._indexCache;
+ // evaluate whether our key is cached and, if so, whether it is up to date
+ if( !cache.hasOwnProperty(key) || self.$keyAt(cache[key]) !== key ) {
+ // update the hashmap
+ var pos = self.$list.findIndex(function(rec) { return self.$$getKey(rec) === key; });
+ if( pos !== -1 ) {
+ cache[key] = pos;
+ }
+ }
+ return cache.hasOwnProperty(key)? cache[key] : -1;
+ },
+
+ /**
+ * The loaded method is invoked after the initial batch of data arrives from the server.
+ * When this resolves, all data which existed prior to calling $asArray() is now cached
+ * locally in the array.
+ *
+ * As a shortcut is also possible to pass resolve/reject methods directly into this
+ * method just as they would be passed to .then()
+ *
+ * @param {Function} [resolve]
+ * @param {Function} [reject]
+ * @returns a promise
+ */
+ $loaded: function(resolve, reject) {
+ var promise = this._promise;
+ if( arguments.length ) {
+ promise = promise.then.call(promise, resolve, reject);
+ }
+ return promise;
+ },
+
+ /**
+ * @returns the original $firebase object used to create this object.
+ */
+ $inst: function() { return this._inst; },
+
+ /**
+ * Listeners passed into this method are notified whenever a new change (add, updated,
+ * move, remove) is received from the server. Each invocation is sent an object
+ * containing { type: 'added|updated|moved|removed', key: 'key_of_item_affected'}
+ *
+ * Additionally, added and moved events receive a prevChild parameter, containing the
+ * key of the item before this one in the array.
+ *
+ * This method returns a function which can be invoked to stop observing events.
+ *
+ * @param {Function} cb
+ * @param {Object} [context]
+ * @returns {Function} used to stop observing
+ */
+ $watch: function(cb, context) {
+ var list = this._observers;
+ list.push([cb, context]);
+ // an off function for cancelling the listener
+ return function() {
+ var i = list.findIndex(function(parts) {
+ return parts[0] === cb && parts[1] === context;
+ });
+ if( i > -1 ) {
+ list.splice(i, 1);
+ }
+ };
+ },
+
+ /**
+ * Informs $firebase to stop sending events and clears memory being used
+ * by this array (delete's its local content).
+ */
+ $destroy: function(err) {
+ if( !this._isDestroyed ) {
+ this._isDestroyed = true;
+ this.$list.length = 0;
+ $log.debug('destroy called for FirebaseArray: '+this.$inst().$ref().toString());
+ this._destroyFn(err);
+ }
+ },
+
+ /**
+ * Returns the record for a given Firebase key (record.$id). If the record is not found
+ * then returns null.
+ *
+ * @param {string} key
+ * @returns {Object|null} a record in this array
+ */
+ $getRecord: function(key) {
+ var i = this.$indexFor(key);
+ return i > -1? this.$list[i] : null;
+ },
+
+ /**
+ * Called by $firebase to inform the array when a new item has been added at the server.
+ * This method must exist on any array factory used by $firebase.
+ *
+ * @param {object} snap a Firebase snapshot
+ * @param {string} prevChild
+ * @return {object} the record to be inserted into the array
+ */
+ $$added: function(snap/*, prevChild*/) {
+ // check to make sure record does not exist
+ var i = this.$indexFor($firebaseUtils.getKey(snap));
+ if( i === -1 ) {
+ // parse data and create record
+ var rec = snap.val();
+ if( !angular.isObject(rec) ) {
+ rec = { $value: rec };
+ }
+ rec.$id = $firebaseUtils.getKey(snap);
+ rec.$priority = snap.getPriority();
+ $firebaseUtils.applyDefaults(rec, this.$$defaults);
+
+ return rec;
+ }
+ return false;
+ },
+
+ /**
+ * Called by $firebase whenever an item is removed at the server.
+ * This method does not physically remove the objects, but instead
+ * returns a boolean indicating whether it should be removed (and
+ * taking any other desired actions before the remove completes).
+ *
+ * @param {object} snap a Firebase snapshot
+ * @return {boolean} true if item should be removed
+ */
+ $$removed: function(snap) {
+ return this.$indexFor($firebaseUtils.getKey(snap)) > -1;
+ },
+
+ /**
+ * Called by $firebase whenever an item is changed at the server.
+ * This method should apply the changes, including changes to data
+ * and to $priority, and then return true if any changes were made.
+ *
+ * @param {object} snap a Firebase snapshot
+ * @return {boolean} true if any data changed
+ */
+ $$updated: function(snap) {
+ var changed = false;
+ var rec = this.$getRecord($firebaseUtils.getKey(snap));
+ if( angular.isObject(rec) ) {
+ // apply changes to the record
+ changed = $firebaseUtils.updateRec(rec, snap);
+ $firebaseUtils.applyDefaults(rec, this.$$defaults);
+ }
+ return changed;
+ },
+
+ /**
+ * Called by $firebase whenever an item changes order (moves) on the server.
+ * This method should set $priority to the updated value and return true if
+ * the record should actually be moved. It should not actually apply the move
+ * operation.
+ *
+ * @param {object} snap a Firebase snapshot
+ * @param {string} prevChild
+ */
+ $$moved: function(snap/*, prevChild*/) {
+ var rec = this.$getRecord($firebaseUtils.getKey(snap));
+ if( angular.isObject(rec) ) {
+ rec.$priority = snap.getPriority();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Called whenever a security error or other problem causes the listeners to become
+ * invalid. This is generally an unrecoverable error.
+ * @param {Object} err which will have a `code` property and possibly a `message`
+ */
+ $$error: function(err) {
+ $log.error(err);
+ this.$destroy(err);
+ },
+
+ /**
+ * Returns ID for a given record
+ * @param {object} rec
+ * @returns {string||null}
+ * @private
+ */
+ $$getKey: function(rec) {
+ return angular.isObject(rec)? rec.$id : null;
+ },
+
+ /**
+ * Handles placement of recs in the array, sending notifications,
+ * and other internals. Called by the $firebase synchronization process
+ * after $$added, $$updated, $$moved, and $$removed.
+ *
+ * @param {string} event one of child_added, child_removed, child_moved, or child_changed
+ * @param {object} rec
+ * @param {string} [prevChild]
+ * @private
+ */
+ $$process: function(event, rec, prevChild) {
+ var key = this.$$getKey(rec);
+ var changed = false;
+ var curPos;
+ switch(event) {
+ case 'child_added':
+ curPos = this.$indexFor(key);
+ break;
+ case 'child_moved':
+ curPos = this.$indexFor(key);
+ this._spliceOut(key);
+ break;
+ case 'child_removed':
+ // remove record from the array
+ changed = this._spliceOut(key) !== null;
+ break;
+ case 'child_changed':
+ changed = true;
+ break;
+ default:
+ throw new Error('Invalid event type: ' + event);
+ }
+ if( angular.isDefined(curPos) ) {
+ // add it to the array
+ changed = this._addAfter(rec, prevChild) !== curPos;
+ }
+ if( changed ) {
+ // send notifications to anybody monitoring $watch
+ this.$$notify(event, key, prevChild);
+ }
+ return changed;
+ },
+
+ /**
+ * Used to trigger notifications for listeners registered using $watch
+ *
+ * @param {string} event
+ * @param {string} key
+ * @param {string} [prevChild]
+ * @private
+ */
+ $$notify: function(event, key, prevChild) {
+ var eventData = {event: event, key: key};
+ if( angular.isDefined(prevChild) ) {
+ eventData.prevChild = prevChild;
+ }
+ angular.forEach(this._observers, function(parts) {
+ parts[0].call(parts[1], eventData);
+ });
+ },
+
+ /**
+ * Used to insert a new record into the array at a specific position. If prevChild is
+ * null, is inserted first, if prevChild is not found, it is inserted last, otherwise,
+ * it goes immediately after prevChild.
+ *
+ * @param {object} rec
+ * @param {string|null} prevChild
+ * @private
+ */
+ _addAfter: function(rec, prevChild) {
+ var i;
+ if( prevChild === null ) {
+ i = 0;
+ }
+ else {
+ i = this.$indexFor(prevChild)+1;
+ if( i === 0 ) { i = this.$list.length; }
+ }
+ this.$list.splice(i, 0, rec);
+ this._indexCache[this.$$getKey(rec)] = i;
+ return i;
+ },
+
+ /**
+ * Removes a record from the array by calling splice. If the item is found
+ * this method returns it. Otherwise, this method returns null.
+ *
+ * @param {string} key
+ * @returns {object|null}
+ * @private
+ */
+ _spliceOut: function(key) {
+ var i = this.$indexFor(key);
+ if( i > -1 ) {
+ delete this._indexCache[key];
+ return this.$list.splice(i, 1)[0];
+ }
+ return null;
+ },
+
+ /**
+ * Resolves a variable which may contain an integer or an item that exists in this array.
+ * Returns the item or null if it does not exist.
+ *
+ * @param indexOrItem
+ * @returns {*}
+ * @private
+ */
+ _resolveItem: function(indexOrItem) {
+ var list = this.$list;
+ if( angular.isNumber(indexOrItem) && indexOrItem >= 0 && list.length >= indexOrItem ) {
+ return list[indexOrItem];
+ }
+ else if( angular.isObject(indexOrItem) ) {
+ // it must be an item in this array; it's not sufficient for it just to have
+ // a $id or even a $id that is in the array, it must be an actual record
+ // the fastest way to determine this is to use $getRecord (to avoid iterating all recs)
+ // and compare the two
+ var key = this.$$getKey(indexOrItem);
+ var rec = this.$getRecord(key);
+ return rec === indexOrItem? rec : null;
+ }
+ return null;
+ },
+
+ /**
+ * Throws an error if $destroy has been called. Should be used for any function
+ * which tries to write data back to $firebase.
+ * @param {string} method
+ * @private
+ */
+ _assertNotDestroyed: function(method) {
+ if( this._isDestroyed ) {
+ throw new Error('Cannot call ' + method + ' method on a destroyed $FirebaseArray object');
+ }
+ }
+ };
+
+ /**
+ * This method allows FirebaseArray to be copied into a new factory. Methods passed into this
+ * function will be added onto the array's prototype. They can override existing methods as
+ * well.
+ *
+ * In addition to passing additional methods, it is also possible to pass in a class function.
+ * The prototype on that class function will be preserved, and it will inherit from
+ * FirebaseArray. It's also possible to do both, passing a class to inherit and additional
+ * methods to add onto the prototype.
+ *
+ * Once a factory is obtained by this method, it can be passed into $firebase as the
+ * `arrayFactory` parameter:
+ *
+ * var MyFactory = $FirebaseArray.$extendFactory({
+ * // add a method onto the prototype that sums all items in the array
+ * getSum: function() {
+ * var ct = 0;
+ * angular.forEach(this.$list, function(rec) { ct += rec.x; });
+ * return ct;
+ * }
+ * });
+ *
+ * // use our new factory in place of $FirebaseArray
+ * var list = $firebase(ref, {arrayFactory: MyFactory}).$asArray();
+ *
+ *
+ * @param {Function} [ChildClass] a child class which should inherit FirebaseArray
+ * @param {Object} [methods] a list of functions to add onto the prototype
+ * @returns {Function} a new factory suitable for use with $firebase
+ */
+ FirebaseArray.$extendFactory = function(ChildClass, methods) {
+ if( arguments.length === 1 && angular.isObject(ChildClass) ) {
+ methods = ChildClass;
+ ChildClass = function() { return FirebaseArray.apply(this, arguments); };
+ }
+ return $firebaseUtils.inherit(ChildClass, FirebaseArray, methods);
+ };
+
+ return FirebaseArray;
+ }
+ ]);
+})();
+
+(function() {
+ 'use strict';
+ var FirebaseAuth;
+
+ // Define a service which provides user authentication and management.
+ angular.module('firebase').factory('$firebaseAuth', [
+ '$q', '$firebaseUtils', '$log', function($q, $firebaseUtils, $log) {
+ /**
+ * This factory returns an object allowing you to manage the client's authentication state.
+ *
+ * @param {Firebase} ref A Firebase reference to authenticate.
+ * @return {object} An object containing methods for authenticating clients, retrieving
+ * authentication state, and managing users.
+ */
+ return function(ref) {
+ var auth = new FirebaseAuth($q, $firebaseUtils, $log, ref);
+ return auth.construct();
+ };
+ }
+ ]);
+
+ FirebaseAuth = function($q, $firebaseUtils, $log, ref) {
+ this._q = $q;
+ this._utils = $firebaseUtils;
+ this._log = $log;
+
+ if (typeof ref === 'string') {
+ throw new Error('Please provide a Firebase reference instead of a URL when creating a `$firebaseAuth` object.');
+ }
+ this._ref = ref;
+ };
+
+ FirebaseAuth.prototype = {
+ construct: function() {
+ this._object = {
+ // Authentication methods
+ $authWithCustomToken: this.authWithCustomToken.bind(this),
+ $authAnonymously: this.authAnonymously.bind(this),
+ $authWithPassword: this.authWithPassword.bind(this),
+ $authWithOAuthPopup: this.authWithOAuthPopup.bind(this),
+ $authWithOAuthRedirect: this.authWithOAuthRedirect.bind(this),
+ $authWithOAuthToken: this.authWithOAuthToken.bind(this),
+ $unauth: this.unauth.bind(this),
+
+ // Authentication state methods
+ $onAuth: this.onAuth.bind(this),
+ $getAuth: this.getAuth.bind(this),
+ $requireAuth: this.requireAuth.bind(this),
+ $waitForAuth: this.waitForAuth.bind(this),
+
+ // User management methods
+ $createUser: this.createUser.bind(this),
+ $changePassword: this.changePassword.bind(this),
+ $changeEmail: this.changeEmail.bind(this),
+ $removeUser: this.removeUser.bind(this),
+ $resetPassword: this.resetPassword.bind(this),
+ $sendPasswordResetEmail: this.sendPasswordResetEmail.bind(this)
+ };
+
+ return this._object;
+ },
+
+
+ /********************/
+ /* Authentication */
+ /********************/
+
+ /**
+ * Authenticates the Firebase reference with a custom authentication token.
+ *
+ * @param {string} authToken An authentication token or a Firebase Secret. A Firebase Secret
+ * should only be used for authenticating a server process and provides full read / write
+ * access to the entire Firebase.
+ * @param {Object} [options] An object containing optional client arguments, such as configuring
+ * session persistence.
+ * @return {Promise