Skip to content

Latest commit

 

History

History
1282 lines (1010 loc) · 43.4 KB

reference.md

File metadata and controls

1282 lines (1010 loc) · 43.4 KB

API Reference | AngularFire

Table of Contents

Initialization

var app = angular.module("app", ["firebase"]);
app.config(function() {
  var config = {
    apiKey: "<API_Key>",               // Your Firebase API key
    authDomain: "<AUTH_DOMAIN>",       // Your Firebase Auth domain ("*.firebaseapp.com")
    databaseURL: "<DATABASE_URL>",     // Your Firebase Database URL ("https://*.firebaseio.com")
    storageBucket: "<STORAGE_BUCKET>"  // Your Firebase Storage bucket ("*.appspot.com")
  };
  firebase.initializeApp(config);
});

$firebaseObject

The $firebaseObject service takes an optional firebase.database.Reference or firebase.database.Query and returns a JavaScript object which contains the data at the provided location in Firebase and some extra AngularFire-specific fields. If no Reference or Query is provided, then the root of the Firebase Database will be used. Note that the data will not be available immediately since retrieving it is an asynchronous operation. You can use the $loaded() promise to get notified when the data has loaded.

This service automatically keeps local objects in sync with any changes made to the remote Firebase database. However, note that any changes to that object will not automatically result in any changes to the remote data. All such changes will have to be performed by updating the object directly and then calling $save() on the object, or by utilizing $bindTo() (see more below).

app.controller("MyCtrl", ["$scope", "$firebaseObject",
  function($scope, $firebaseObject) {
     var ref = firebase.database().ref();

     var obj = $firebaseObject(ref);

     // to take an action after the data loads, use the $loaded() promise
     obj.$loaded().then(function() {
        console.log("loaded record:", obj.$id, obj.someOtherKeyInData);

       // To iterate the key/value pairs of the object, use angular.forEach()
       angular.forEach(obj, function(value, key) {
          console.log(key, value);
       });
     });

     // To make the data available in the DOM, assign it to $scope
     $scope.data = obj;

     // For three-way data bindings, bind it to the scope instead
     obj.$bindTo($scope, "data");
  }
]);

$id

The key where this record is stored. The same as obj.$ref().key.

$priority

The priority for this record according to the last update we received. Modifying this value and then calling $save() will also update the server's priority.

IMPORTANT NOTE: Because Angular's $watch() function ignores keys prefixed with $, changing this field inside the $bindTo() function will not trigger an update unless a field without a $ prefix is also updated. It is best to avoid using $bindTo() for editing $ variables and just rely on the $save() method.

$value

If the value in the database is a primitive (boolean, string, or number) then the value will be stored under this $value key. Modifying this value and then calling $save() will also update the server's value.

Note that any time other keys exist, this one will be ignored. To change an object to a primitive value, delete the other keys and add this key to the object. As a shortcut, we can use:

var obj = $firebaseObject(ref); // an object with data keys
$firebaseUtils.updateRec(obj, newPrimitiveValue); // updateRec will delete the other keys for us

IMPORTANT NOTE: Because Angular's $watch() function ignores keys prefixed with $, changing this field inside the $bindTo() function will not trigger an update unless a field without a $ prefix is also updated. It is best to avoid using $bindTo() for editing $ variables and just rely on the $save() method.

$remove()

Removes the entire object locally and from the database. This method returns a promise that will be fulfilled when the data has been removed from the server. The promise will be resolved with a Firebase reference for the exterminated record.

var obj = $firebaseObject(ref);
obj.$remove().then(function(ref) {
  // data has been deleted locally and in the database
}, function(error) {
  console.log("Error:", error);
});

$save()

If changes are made to data, then calling $save() will push those changes to the server. This method returns a promise that will resolve with this object's Firebase reference when the write is completed.

var obj = $firebaseObject(ref);
obj.foo = "bar";
obj.$save().then(function(ref) {
  ref.key === obj.$id; // true
}, function(error) {
  console.log("Error:", error);
});

$loaded()

Returns a promise which is resolved asynchronously when the initial object data has been downloaded from the database. The promise resolves to the $firebaseObject itself.

var obj = $firebaseObject(ref);
obj.$loaded()
  .then(function(data) {
    console.log(data === obj); // true
  })
  .catch(function(error) {
    console.error("Error:", error);
  });

As a shortcut, the resolve() / reject() methods can optionally be passed directly into $loaded():

var obj = $firebaseObject(ref);
obj.$loaded(
  function(data) {
    console.log(data === obj); // true
  },
  function(error) {
    console.error("Error:", error);
  }
);

$ref()

Returns the Firebase reference used to create this object.

var obj = $firebaseObject(ref);
obj.$ref() === ref; // true

$bindTo(scope, varName)

Creates a three-way binding between a scope variable and the database data. When the scope data is updated, changes are pushed to the database, and when changes occur in the database, they are pushed instantly into scope. This method returns a promise that resolves after the initial value is pulled from the database and set in the scope variable.

var ref = firebase.database().ref(); // assume value here is { foo: "bar" }
var obj = $firebaseObject(ref);

obj.$bindTo($scope, "data").then(function() {
  console.log($scope.data); // { foo: "bar" }
  $scope.data.foo = "baz";  // will be saved to the database
  ref.set({ foo: "baz" });  // this would update the database and $scope.data
});

We can now bind to any property on our object directly in the HTML, and have it saved instantly to the database. Security and Firebase Rules can be used for validation to ensure data is formatted correctly at the server.

<!--
  This input field has three-way data binding to the database
  (changing value updates remote data; remote changes are applied here)
-->
<input type="text" ng-model="data.foo" />

Only one scope variable can be bound at a time. If a second attempts to bind to the same $firebaseObject instance, the promise will be rejected and the bind will fail.

IMPORTANT NOTE: Angular does not report variables prefixed with $ to any $watch() listeners. a simple workaround here is to use a variable prefixed with _, which will not be saved to the server, but will trigger $watch().

var obj = $firebaseObject(ref);
obj.$bindTo($scope, "widget").then(function() {
  $scope.widget.$priority = 99;
  $scope.widget._updated = true;
})

If $destroy() is emitted by scope (this happens when a controller is destroyed), then this object is automatically unbound from scope. It can also be manually unbound using the unbind() method, which is passed into the promise callback.

var obj = $firebaseObject(ref);
obj.$bindTo($scope, "data").then(function(unbind) {
  // unbind this later
  //unbind();
});

$watch(callback, context)

Registers an event listener which will be notified any time there is a change to the data. Returns an unregister function that, when invoked, will stop notifying the callback of changes.

var obj = $firebaseObject(ref);
var unwatch = obj.$watch(function() {
  console.log("data changed!");
});

// at some time in the future, we can unregister using
unwatch();

$destroy()

Calling this method cancels event listeners and frees memory used by this object (deletes the local data). Changes are no longer synchronized to or from the database.

$resolved

Attribute which represents the loaded state for this object. Its value will be true if the initial object data has been downloaded from the database; otherwise, its value will be false. This attribute is complementary to the $loaded() method. If the $loaded() promise is completed (either with success or rejection), then $resolved will be true. $resolved will be false before that.

Knowing if the object has been resolved is useful to conditionally show certain parts of your view:

$scope.obj = $firebaseObject(ref);
<!-- Loading state -->
<div ng-if="!obj.$resolved">
  ...
</div>

<!-- Loaded state -->
<div ng-if="obj.$resolved">
  ...
</div>

$firebaseArray

The $firebaseArray service takes an optional firebase.database.Reference or firebase.database.Query and returns a JavaScript array which contains the data at the provided location in Firebase and some extra AngularFire-specific fields. If no Reference or Query is provided, then the root of the Firebase Database will be used. Note that the data will not be available immediately since retrieving it is an asynchronous operation. You can use the $loaded() promise to get notified when the data has loaded.

This service automatically keeps this local array in sync with any changes made to the remote database. This is a PSEUDO READ-ONLY ARRAY suitable for use in directives like ng-repeat and with Angular filters (which expect an array).

While using read attributes and methods like length and toString() will work great on this array, you should avoid directly manipulating the array. Methods like splice(), push(), pop(), shift(), unshift(), and reverse() will cause the local data to become out of sync with the server. Instead, utilize the $add(), $remove(), and $save() methods provided by the service to change the structure of the array. To get the id of an item in a $firebaseArray within ng-repeat, call $id on that item.

// JavaScript
app.controller("MyCtrl", ["$scope", "$firebaseArray",
  function($scope, $firebaseArray) {
    var ref = firebase.database().ref();
    var list = $firebaseArray(ref);

    // add an item
    list.$add({ foo: "bar" }).then(...);

    // remove an item
    list.$remove(2).then(...);

    // make the list available in the DOM
    $scope.list = list;
  }
]);
<!-- HTML -->
<li ng-repeat="item in list | filter:name">{{ item | json }}</li>

The $firebaseArray service can also take a query to only sync a subset of data.

app.controller("MyCtrl", ["$scope", "$firebaseArray",
  function($scope, $firebaseArray) {
    var ref = firebase.database().ref();
    var messagesRef = ref.child("messages");
    var query = messagesRef.orderByChild("timestamp").limitToLast(10);

    var list = $firebaseArray(query);
  }
]);

Note that, while the array itself should not be modified, it is practical to change specific elements of the array and save them back to the remote database:

// JavaScript
var list = $firebaseArray(ref);
list[2].foo = "bar";
list.$save(2);
<!-- HTML -->
<li ng-repeat="item in list">
  <input ng-model="item.foo" ng-change="list.$save(item)" />
</li>

$add(newData)

Creates a new record in the database and adds the record to our local synchronized array.

This method returns a promise which is resolved after data has been saved to the server. The promise resolves to the Firebase reference for the newly added record, providing easy access to its key.

var list = $firebaseArray(ref);
list.$add({ foo: "bar" }).then(function(ref) {
  var id = ref.key;
  console.log("added record with id " + id);
  list.$indexFor(id); // returns location in the array
});

$remove(recordOrIndex)

Remove a record from the database and from the local array. This method returns a promise that resolves after the record is deleted at the server. It will contain a Firebase reference to the deleted record. It accepts either an array index or a reference to an item that exists in the array.

var list = $firebaseArray(ref);
var item = list[2];
list.$remove(item).then(function(ref) {
  ref.key === item.$id; // true
});

$save(recordOrIndex)

The array itself cannot be modified, but records in the array can be updated and saved back to the database individually. This method saves an existing, modified local record back to the database. It accepts either an array index or a reference to an item that exists in the array.

$scope.list = $firebaseArray(ref);
<li ng-repeat="item in list">
  <input type="text" ng-model="item.title" ng-change="list.$save(item)" />
</li>

This method returns a promise which is resolved after data has been saved to the server. The promise resolves to the Firebase reference for the saved record, providing easy access to its key.

var list = $firebaseArray(ref);
list[2].foo = "bar";
list.$save(2).then(function(ref) {
  ref.key === list[2].$id; // true
});

$getRecord(key)

Returns the record from the array for the given key. If the key is not found, returns null. This method utilizes $indexFor(key) to find the appropriate record.

var list = $firebaseArray(ref);
var rec = list.$getRecord("foo"); // record with $id === "foo" or null

$keyAt(recordOrIndex)

Returns the key for a record in the array. It accepts either an array index or a reference to an item that exists in the array.

// assume records "alpha", "bravo", and "charlie"
var list = $firebaseArray(ref);
list.$keyAt(1); // bravo
list.$keyAt( list[1] ); // bravo

$indexFor(key)

The inverse of $keyAt(), this method takes a key and finds the associated record in the array. If the record does not exist, -1 is returned.

// assume records "alpha", "bravo", and "charlie"
var list = $firebaseArray(ref);
list.$indexFor("alpha"); // 0
list.$indexFor("bravo"); // 1
list.$indexFor("zulu"); // -1

$loaded()

Returns a promise which is resolved asynchronously when the initial array data has been downloaded from the database. The promise resolves to the $firebaseArray.

var list = $firebaseArray(ref);
list.$loaded()
  .then(function(x) {
    x === list; // true
  })
  .catch(function(error) {
    console.log("Error:", error);
  });

The resolve/reject methods may also be passed directly into $loaded:

var list = $firebaseArray(ref);
list.$loaded(
  function(x) {
    x === list; // true
  }, function(error) {
    console.error("Error:", error);
  });

$ref()

Returns the Firebase reference used to create this array.

var list = $firebaseArray(ref);
sync === list.$ref(); // true

$watch(cb[, context])

Any callback passed here will be invoked each time data in the array is updated from the server. The callback receives an object with the following keys:

  • event: The database event type which fired (child_added, child_moved, child_removed, or child_changed).
  • key: The ID of the record that triggered the event.
  • prevChild: If event is child_added or child_moved, this contains the previous record's key or null if key belongs to the first record in the collection.
var list = $firebaseArray(ref);

list.$watch(function(event) {
  console.log(event);
});

// logs { event: "child_removed", key: "foo" }
list.$remove("foo");

// logs { event: "child_added", key: "<new_id>", prevId: "<prev_id>" }
list.$add({ hello: "world" });

A common use case for this would be to customize the sorting for a synchronized array. Since each time an add or update arrives from the server, the data could become out of order, we can re-sort on each event. We don't have to worry about excessive re-sorts slowing down Angular's compile process, or creating excessive DOM updates, because the events are already batched nicely into a single $apply event (we gather them up and trigger the events in batches before telling $digest to dirty check).

var list = $firebaseArray(ref);

// sort our list
list.sort(compare);

// each time the server sends records, re-sort
list.$watch(function() { list.sort(compare); });

// custom sorting routine (sort by last name)
function compare(a, b) {
  return a.lastName.localeCompare(b.lastName);
}

$destroy()

Stop listening for events and free memory used by this array (empties the local copy). Changes are no longer synchronized to or from the database.

$resolved

Attribute which represents the loaded state for this array. Its value will be true if the initial array data has been downloaded from the database; otherwise, its value will be false. This attribute is complementary to the $loaded() method. If the $loaded() promise is completed (either with success or rejection), then $resolved will be true. $resolved will be false before that.

Knowing if the array has been resolved is useful to conditionally show certain parts of your view:

$scope.list = $firebaseArray(ref);
<!-- Loading state -->
<div ng-if="!list.$resolved">
  ...
</div>

<!-- Loaded state -->
<div ng-if="list.$resolved">
  ...
</div>

$firebaseAuth

AngularFire includes support for user authentication and management with the $firebaseAuth service.

The $firebaseAuth factory takes an optional Firebase auth instance (firebase.auth()) as its only argument. Note that the authentication state is global to your application, even if multiple $firebaseAuth objects are created unless you use multiple Firebase apps.

app.controller("MyAuthCtrl", ["$scope", "$firebaseAuth",
  function($scope, $firebaseAuth) {
    $scope.authObj = $firebaseAuth();
  }
]);

The authentication object returned by $firebaseAuth contains several methods for authenticating users, responding to changes in authentication state, and managing user accounts for email / password users.

$signInWithCustomToken(authToken)

Authenticates the client using a custom authentication token. This function takes two arguments: an authentication token or a Firebase Secret and an object containing optional client arguments, such as configuring session persistence.

$scope.authObj.$signInWithCustomToken("<CUSTOM_AUTH_TOKEN>").then(function(firebaseUser) {
  console.log("Signed in as:", firebaseUser.uid);
}).catch(function(error) {
  console.error("Authentication failed:", error);
});

This method returns a promise which is resolved or rejected when the authentication attempt is completed. If successful, the promise will be fulfilled with an object containing the payload of the authentication token. If unsuccessful, the promise will be rejected with an Error object.

Read our Custom Authentication guide for more details about generating your own custom authentication tokens.

$signInAnonymously()

Authenticates the client using a new, temporary guest account.

$scope.authObj.$signInAnonymously().then(function(firebaseUser) {
  console.log("Signed in as:", firebaseUser.uid);
}).catch(function(error) {
  console.error("Authentication failed:", error);
});

This method returns a promise which is resolved or rejected when the authentication attempt is completed. If successful, the promise will be fulfilled with an object containing authentication data about the signed-in user. If unsuccessful, the promise will be rejected with an Error object.

Read our documentation on anonymous authentication for more details about anonymous authentication.

$signInWithEmailAndPassword(email, password)

Authenticates the client using an email / password combination. This function takes two arguments: an object containing email and password attributes corresponding to the user account and an object containing optional client arguments, such as configuring session persistence.

$scope.authObj.$signInWithEmailAndPassword("[email protected]", "password").then(function(firebaseUser) {
  console.log("Signed in as:", firebaseUser.uid);
}).catch(function(error) {
  console.error("Authentication failed:", error);
});

This method returns a promise which is resolved or rejected when the authentication attempt is completed. If successful, the promise will be fulfilled with an object containing authentication data about the signed-in user. If unsuccessful, the promise will be rejected with an Error object.

Read our documentation on email / password authentication for more details about email / password authentication.

$signInWithPopup(provider)

Authenticates the client using a popup-based OAuth flow. This function takes two arguments: the unique string identifying the OAuth provider to authenticate with (e.g. "google").

Optionally, you can pass a provider object (like new firebase.auth.GoogleAuthProvider(), etc) which can be configured with additional options.

$scope.authObj.$signInWithPopup("google").then(function(result) {
  console.log("Signed in as:", result.user.uid);
}).catch(function(error) {
  console.error("Authentication failed:", error);
});

This method returns a promise which is resolved or rejected when the authentication attempt is completed. If successful, the promise will be fulfilled with an object containing authentication data about the signed-in user. If unsuccessful, the promise will be rejected with an Error object.

Firebase currently supports Facebook, GitHub, Google, and Twitter authentication. Refer to authentication documentation for information about configuring each provider.

$signInWithRedirect(provider[, options])

Authenticates the client using a redirect-based OAuth flow. This function takes two arguments: the unique string identifying the OAuth provider to authenticate with (e.g. "google").

Optionally, you can pass a provider object (like new firebase.auth().GoogleProvider(), etc) which can be configured with additional options.

$scope.authObj.$signInWithRedirect("google").then(function() {
  // Never called because of page redirect
}).catch(function(error) {
  console.error("Authentication failed:", error);
});

This method returns a rejected promise with an Error object if the authentication attempt fails. Upon successful authentication, the browser will be redirected as part of the OAuth authentication flow. As such, the returned promise will never be fulfilled. Instead, you should use the $onAuthStateChanged() method to detect when the authentication has been successfully completed.

Firebase currently supports Facebook, GitHub, Google, and Twitter authentication. Refer to authentication documentation for information about configuring each provider.

$signInWithCredential(credential)

Authenticates the client using a credential (potentially created from OAuth Tokens). This function takes one arguments: the credential object. This may be obtained from individual auth providers under firebase.auth();

$scope.authObj.$signInWithCredential(credential).then(function(firebaseUser) {
  console.log("Signed in as:", firebaseUser.uid);
}).catch(function(error) {
  console.error("Authentication failed:", error);
});

This method returns a promise which is resolved or rejected when the authentication attempt is completed. If successful, the promise will be fulfilled with an object containing authentication data about the signed-in user. If unsuccessful, the promise will be rejected with an Error object.

Firebase currently supports Facebook, GitHub, Google, and Twitter authentication. Refer to authentication documentation for information about configuring each provider.

$getAuth()

Synchronously retrieves the current authentication state of the client. If the user is authenticated, an object containing the fields uid (the unique user ID), provider (string identifying the provider), auth (the authentication token payload), and expires (expiration time in seconds since the Unix epoch) - and more, depending upon the provider used to authenticate - will be returned. Otherwise, the return value will be null.

var firebaseUser = $scope.authObj.$getAuth();

if (firebaseUser) {
  console.log("Signed in as:", firebaseUser.uid);
} else {
  console.log("Signed out");
}

$onAuthStateChanged(callback[, context])

Listens for changes to the client's authentication state. The provided callback will fire when the client's authenticate state changes. If authenticated, the callback will be passed an object containing the fields uid (the unique user ID), provider (string identifying the provider), auth (the authentication token payload), and expires (expiration time in seconds since the Unix epoch) - and more, depending upon the provider used to authenticate. Otherwise, the callback will be passed null.

$scope.authObj.$onAuthStateChanged(function(firebaseUser) {
  if (firebaseUser) {
    console.log("Signed in as:", firebaseUser.uid);
  } else {
    console.log("Signed out");
  }
});

This method can also take an optional second argument which, if provided, will be used as this when calling your callback.

This method returns a function which can be used to unregister the provided callback. Once the callback is unregistered, changes in authentication state will not cause the callback to fire.

var offAuth = $scope.authObj.$onAuthStateChanged(callback);

// ... sometime later, unregister the callback
offAuth();

$signOut()

Signs out a client. It takes no arguments and returns an empty Promise when the client has been signed out. Upon fulfillment, the $onAuthStateChanged() callback(s) will be triggered.

<span ng-show="firebaseUser">
  {{ firebaseUser.displayName }} | <a href="#" ng-click="authObj.$signOut()">Sign out</a>
</span>

$createUserWithEmailAndPassword(email, password)

Creates a new user account using an email / password combination. This function returns a promise that is resolved with an object containing user data about the created user.

$scope.authObj.$createUserWithEmailAndPassword("[email protected]", "mypassword")
  .then(function(firebaseUser) {
    console.log("User " + firebaseUser.uid + " created successfully!");
  }).catch(function(error) {
    console.error("Error: ", error);
  });

Note that this function both creates the new user and authenticates as the new user.

$updatePassword(newPassword)

Changes the password of the currently signed-in user. This function returns a promise that is resolved when the password has been successfully changed on the Firebase Authentication servers.

$scope.authObj.$updatePassword("newPassword").then(function() {
  console.log("Password changed successfully!");
}).catch(function(error) {
  console.error("Error: ", error);
});

$updateEmail(newEmail)

Changes the email of the currently signed-in user. This function returns a promise that is resolved when the email has been successfully changed on the Firebase Authentication servers.

$scope.authObj.$updateEmail("[email protected]").then(function() {
  console.log("Email changed successfully!");
}).catch(function(error) {
  console.error("Error: ", error);
});

$deleteUser()

Deletes the currently authenticated user. This function returns a promise that is resolved when the user has been successfully removed on the Firebase Authentication servers.

$scope.authObj.$deleteUser().then(function() {
  console.log("User removed successfully!");
}).catch(function(error) {
  console.error("Error: ", error);
});

Note that removing a user also logs that user out and will therefore fire any onAuthStateChanged() callbacks that you have created.

$sendPasswordResetEmail(email)

Sends a password-reset email to the owner of the account, containing a token that may be used to authenticate and change the user's password. This function returns a promise that is resolved when the email notification has been sent successfully.

$scope.authObj.$sendPasswordResetEmail("[email protected]").then(function() {
  console.log("Password reset email sent successfully!");
}).catch(function(error) {
  console.error("Error: ", error);
});

$waitForSignIn()

Helper method which returns a promise fulfilled with the current authentication state. This is intended to be used in the resolve() method of Angular routers. See the "Using Authentication with Routers" section of our AngularFire guide for more information and a full example.

$requireSignIn()

Helper method which returns a promise fulfilled with the current authentication state if the user is authenticated but otherwise rejects the promise. This is intended to be used in the resolve() method of Angular routers to prevented unauthenticated users from seeing authenticated pages momentarily during page load. See the "Using Authentication with Routers" section of our AngularFire guide for more information and a full example.

Extending the Services

There are several powerful techniques for transforming the data downloaded and saved by $firebaseArray and $firebaseObject. These techniques should only be attempted by advanced Angular users who know their way around the code.

Extending $firebaseObject

You can create a new factory from a $firebaseObject. It can add additional methods or override any existing method.

var ColorFactory = $firebaseObject.$extend({
  getMyFavoriteColor: function() {
    return this.favoriteColor + ", no green!"; // obscure Monty Python reference
  }
});

var factory = new ColorFactory(ref);
var favColor = factory.getMyFavoriteColor();

This technique can also be used to transform how data is stored and saved by overriding the following private methods:

  • $$updated: Called with a snapshot any time a value event is received from the database, must apply the updates and return true if any changes occurred.
  • $$error: Passed an Error any time a security error occurs. These are generally not recoverable.
  • $$notify: Sends notifications to any listeners registered with $watch().
  • toJSON: As with any object, if a toJSON() method is provided, it will be used by JSON.stringify() to parse the JSON content before it is saved to the database.
  • $$defaults: A key / value pair that can be used to create default values for any fields which are not found in the server data (i.e. undefined fields). By default, they are applied each time $$updated is invoked. If that method is overridden, it would need to implement this behavior.
// Add a counter to our object...
var FactoryWithCounter = $firebaseObject.$extend({
  // add a method to the prototype that returns our counter
  getUpdateCount: function() { return this._counter; },

  // each time an update arrives from the server, apply the change locally
  $$updated: function(snap) {
    // apply the changes using the super method
    var changed = $firebaseObject.prototype.$$updated.apply(this, arguments);

    // add / increment a counter each time there is an update
    if( !this._counter ) { this._counter = 0; }
    this._counter++;

    // return whether or not changes occurred
    return changed;
  }
});

Extending $firebaseArray

You can create a new factory from a $firebaseArray. It can add additional methods or override any existing method.

app.factory("ArrayWithSum", function($firebaseArray) {
  return $firebaseArray.$extend({
    sum: function() {
      var total = 0;
      angular.forEach(this.$list, function(rec) {
        total += rec.x;
      });
      return total;
    }
  });
})

We can then use this factory with by instantiating it:

var list = new ArrayWithSum(ref);
list.$loaded().then(function() {
  console.log("List has " + list.sum() + " items");
});

This technique can be used to transform how data is stored by overriding the following private methods:

  • $$added: Called with a snapshot and prevChild any time a child_added event occurs.
  • $$updated: Called with a snapshot any time a child_changed event occurs.
  • $$moved: Called with a snapshot and prevChild any time child_moved event occurs.
  • $$removed: Called with a snapshot any time a child_removed event occurs.
  • $$error: Passed an Error any time a security error occurs. These are generally not recoverable.
  • $$getKey: Tells AngularFire what the unique ID is for each record (the default just returns this.$id).
  • $$notify: Notifies any listeners registered with $watch; normally this method would not be modified.
  • $$process: Handles the actual splicing of data into and out of the array. Normally this method would not be modified.
  • $$defaults: A key / value pair that can be used to create default values for any fields which are not found in the server data (i.e. undefined fields). By default, they are applied each time $$added or $$updated are invoked. If those methods are overridden, they would need to implement this behavior.

To illustrate, let's create a factory that creates Widget instances, and transforms dates:

// an object to return in our WidgetFactory
app.factory("Widget", function($firebaseUtils) {
  function Widget(snapshot) {
    // store the record id so AngularFire can identify it
    this.$id = snapshot.key;

    // apply the data
    this.update(snapshot);
  }

  Widget.prototype = {
    update: function(snapshot) {
      var oldData = angular.extend({}, this.data);

      // apply changes to this.data instead of directly on `this`
      this.data = snapshot.val();

      // add a parsed date to our widget
      this._date = new Date(this.data.date);

      // determine if anything changed, note that angular.equals will not check
      // $value or $priority (since it excludes anything that starts with $)
      // so be careful when using angular.equals()
      return !angular.equals(this.data, oldData);
    },

    getDate: function() {
      return this._date;
    },

    toJSON: function() {
      // since we changed where our data is stored, we need to tell AngularFire how
      // to get the JSON version of it. We can use $firebaseUtils.toJSON() to remove
      // private variables, copy the data into a shippable format, and do validation
      return $firebaseUtils.toJSON(this.data);
    }
  };

  return Widget;
});

// now let's create a synchronized array factory that uses our Widget
app.factory("WidgetFactory", function($firebaseArray, Widget) {
  return $firebaseArray.$extend({
    // change the added behavior to return Widget objects
    $$added: function(snap) {
      // instead of creating the default POJO (plain old JavaScript object)
      // we will return an instance of the Widget class each time a child_added
      // event is received from the server
      return new Widget(snap);
    },

    // override the update behavior to call Widget.update()
    $$updated: function(snap) {
      // we need to return true/false here or $watch listeners will not get triggered
      // luckily, our Widget.prototype.update() method already returns a boolean if
      // anything has changed
      return this.$getRecord(snap.key.update(snap);
    }
  });
});

Passing a Class into $extend

Instead of just a list of functions, we can also pass in a class constructor to inherit methods from $firebaseArray. The prototype for this class will be preserved, and it will inherit from $firebaseArray.

This is an extremely advanced feature. Do not use this unless you know that you need it

This class constructor is expected to call $firebaseArray's constructor (i.e. the super constructor).

The following factory adds an update counter which is incremented each time $$added() or $$updated() is called:

app.factory("ArrayWithCounter", function($firebaseArray, Widget) {
  // $firebaseArray and $firebaseObject constructors both accept a single argument, a `Firebase` ref
  function ArrayWithCounter(ref) {
    // initialize a counter
    this.counter = 0;

    // call the super constructor
    return $firebaseArray.call(this, ref);
  }

  // override the add behavior to return a Widget
  ArrayWithCounter.prototype.$$added = function(snap) {
    return new Widget(snap);
  };

  // override the update behavior to call Widget.update()
  ArrayWithCounter.prototype.$$updated = function(snap) {
    var widget = this.$getRecord(snap.key;
    return widget.update();
  };

  // pass our constructor to $extend, which will automatically extract the
  // prototype methods and call the constructor appropriately
  return $firebaseArray.$extend(ArrayWithCounter);
});

Decorating the Services

In general, it will be more useful to extend the services to create new services than to use this technique. However, it is also possible to modify $firebaseArray or $firebaseObject globally by using Angular's $decorate() method.

app.config(function($provide) {
  $provide.decorator("$firebaseObject", function($delegate, $firebaseUtils) {
    var _super = $delegate.prototype.$$updated;

    // override any instance of $firebaseObject to look for a date field
    // and transforms it to a Date object.
    $delegate.prototype.$$updated = function(snap) {
      var changed = _super.call(this, snap);
      if( this.hasOwnProperty("date") ) {
        this._dateObj = new Date(this.date);
      }
      return changed;
    };

    // add a method that fetches the date object we just created
    $delegate.prototype.getDate = function() {
      return this._dateObj;
    };

    // variables starting with _ are ignored by AngularFire so we don't need
    // to worry about the toJSON method here

    return $delegate;
  });
});

Creating AngularFire Services

With the ability to extend the AngularFire services, services can be built to represent our synchronized collections with a minimal amount of code. For example, we can create a User factory:

// create a User factory with a getFullName() method
app.factory("UserFactory", function($firebaseObject) {
  return $firebaseObject.$extend({
      getFullName: function() {
        // concatenate first and last name
        return this.first_name + " " + this.last_name;
      }
   });
});

And create a new instance:

// create a User object from our Factory
app.factory("User", function(UserFactory) {
  var ref = firebase.database().ref();
  var usersRef = ref.child("users");
  return function(userid) {
    return new UserFactory(usersRef.child(userid));
  }
});

Similarly, we can extend $firebaseArray by creating a Message object:

app.factory("Message", function($firebaseArray) {
  function Message(snap) {
    // store the user ID so AngularFire can identify the records
    // in this case, we store it in a custom location, so we'll need
    // to override $$getKey
    this.message_id = snap.key;
    this.message = snap.val();
  }
  Message.prototype = {
    update: function(snap) {
      // store a string into this.message (instead of the default $value)
      if( snap.val() !== this.message ) {
        this.message = snap.val();
        return true;
      }
      return false;
    },
    toJSON: function() {
      // tell AngularFire what data to save, in this case a string
      return this.message;
    }
  };

  return Message;
});

We can then use that to extend the $firebaseArray factory:

app.factory("MessageFactory", function($firebaseArray, Message) {
  return $firebaseArray.$extend({
    // override the $createObject behavior to return a Message object
    $$added: function(snap) {
      return new Message(snap);
    },

    // override the $$updated behavior to call a method on the Message
    $$updated: function(snap) {
      var msg = this.$getRecord(snap.key);
      return msg.update(snap);
    },

    // our messages store the unique id in a special location, so tell $firebaseArray
    // how to find the id for each record
    $$getKey: function(rec) {
      return rec.message_id;
    }
  });
});

And finally, we can put it all together into a synchronized list of messages:

app.factory("MessageList", function(MessageFactory) {
  return function(ref) {
    return new MessageFactory(ref);
  }
});

SDK Compatibility

This documentation is for AngularFire 2.x.x with Firebase SDK 3.x.x.

SDK Version AngularFire Version Supported
3.x.x 2.x.x
2.x.x 1.x.x

Browser Compatibility

Browser Version Supported With Polyfill
Internet Explorer 9+ 9+ (Angular 1.3 only supports 9+)
Firefox 4.0 3.0?
Chrome 7 5?
Safari 5.1.4 ?
Opera 11.6 ?

Polyfills are automatically included to support older browsers. See src/polyfills.js for links and details.