diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 00000000..3a5cc31a
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,101 @@
+# Contributing | AngularFire
+
+Thank you for contributing to the Firebase community!
+
+ - [Have a usage question?](#question)
+ - [Think you found a bug?](#issue)
+ - [Have a feature request?](#feature)
+ - [Want to submit a pull request?](#submit)
+ - [Need to get set up locally?](#local-setup)
+
+
+## Have a usage question?
+
+We get lots of those and we love helping you, but GitHub is not the best place for them. Issues
+which just ask about usage will be closed. Here are some resources to get help:
+
+- Start with the [quickstart](/docs/quickstart.md)
+- Go through the [guide](/docs/guide/README.md)
+- Read the full [API reference](/docs/reference.md)
+- Try out some [examples](/README.md#examples)
+
+If the official documentation doesn't help, try asking a question on the
+[Firebase Google Group](https://groups.google.com/forum/#!forum/firebase-talk) or one of our
+other [official support channels](https://firebase.google.com/support/).
+
+**Please avoid double posting across multiple channels!**
+
+
+## Think you found a bug?
+
+Yeah, we're definitely not perfect!
+
+Search through [old issues](https://github.com/firebase/angularfire/issues) before submitting a new
+issue as your question may have already been answered.
+
+If your issue appears to be a bug, and hasn't been reported,
+[open a new issue](https://github.com/firebase/angularfire/issues/new). Please use the provided bug
+report template and include a minimal repro.
+
+If you are up to the challenge, [submit a pull request](#submit) with a fix!
+
+
+## Have a feature request?
+
+Great, we love hearing how we can improve our products! After making sure someone hasn't already
+requested the feature in the [existing issues](https://github.com/firebase/angularfire/issues), go
+ahead and [open a new issue](https://github.com/firebase/angularfire/issues/new). Feel free to remove
+the bug report template and instead provide an explanation of your feature request. Provide code
+samples if applicable. Try to think about what it will allow you to do that you can't do today? How
+will it make current workarounds straightforward? What potential bugs and edge cases does it help to
+avoid?
+
+
+## Want to submit a pull request?
+
+Sweet, we'd love to accept your contribution! [Open a new pull request](https://github.com/firebase/angularfire/pull/new/master)
+and fill out the provided form.
+
+**If you want to implement a new feature, please open an issue with a proposal first so that we can
+figure out if the feature makes sense and how it will work.**
+
+Make sure your changes pass our linter and the tests all pass on your local machine. We've hooked
+up this repo with continuous integration to double check those things for you.
+
+Most non-trivial changes should include some extra test coverage. If you aren't sure how to add
+tests, feel free to submit regardless and ask us for some advice.
+
+Finally, you will need to sign our [Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
+before we can accept your pull request.
+
+
+## Need to get set up locally?
+
+If you'd like to contribute to AngularFire, you'll need to do the following to get your environment
+set up.
+
+### Install Dependencies
+
+```bash
+$ git clone https://github.com/firebase/angularfire.git
+$ cd angularfire # go to the angularfire directory
+$ npm install -g grunt-cli # globally install grunt task runner
+$ npm install # install local npm build / test dependencies
+$ grunt install # install Selenium server for end-to-end tests
+```
+
+### Lint, Build, and Test
+
+```bash
+$ grunt # lint, build, and test
+
+$ grunt build # lint and build
+
+$ grunt test # run unit and e2e tests
+$ grunt test:unit # run unit tests
+$ grunt test:e2e # run e2e tests (via Protractor)
+
+$ grunt watch # lint, build, and test whenever source files change
+```
+
+The output files - `angularfire.js` and `angularfire.min.js` - are written to the `/dist/` directory.
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000..c3200067
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,69 @@
+
+
+
+### Version info
+
+
+
+**Angular:**
+
+**Firebase:**
+
+**AngularFire:**
+
+**Other (e.g. Node, browser, operating system) (if applicable):**
+
+### Test case
+
+
+
+
+### Steps to reproduce
+
+
+
+
+### Expected behavior
+
+
+
+
+### Actual behavior
+
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..80efa774
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,31 @@
+
+
+
+### Description
+
+
+
+### Code sample
+
+
diff --git a/.gitignore b/.gitignore
index 7f7cf59b..a6759600 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
-bower_components/
+dist/
node_modules/
bower_components/
-selenium/
-.idea
\ No newline at end of file
+tests/coverage/
+
+.idea
diff --git a/.jshintrc b/.jshintrc
index 3db69b61..c3c48f86 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,22 +1,19 @@
{
- "bitwise" : true,
- "boss" : true,
- "browser" : true,
- "curly" : true,
- "devel" : true,
- "eqnull" : true,
- "globals" : {
- "angular" : false,
- "Firebase" : false,
- "FirebaseSimpleLogin" : false
- },
- "globalstrict" : true,
- "indent" : 2,
- "latedef" : true,
- "maxlen" : 115,
- "noempty" : true,
- "nonstandard" : true,
- "undef" : true,
- "unused" : true,
- "trailing" : true
+ "predef": [
+ "angular",
+ "firebase"
+ ],
+ "bitwise": true,
+ "browser": true,
+ "curly": true,
+ "forin": true,
+ "indent": 2,
+ "latedef": true,
+ "node": true,
+ "noempty": true,
+ "nonbsp": true,
+ "strict": true,
+ "trailing": true,
+ "undef": true,
+ "unused": true
}
diff --git a/.travis.yml b/.travis.yml
index 84eae5d6..5afad1f9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,25 @@
language: node_js
node_js:
- - "0.10"
-branches:
- only:
- - master
+- stable
+sudo: false
+addons:
+ sauce_connect: true
+before_install:
+- export CHROME_BIN=chromium-browser
+- export DISPLAY=:99.0
+- sh -e /etc/init.d/xvfb start
install:
- - git clone git://github.com/n1k0/casperjs.git ~/casperjs
- - export PATH=$PATH:~/casperjs/bin
- - npm install -g grunt-cli
- - npm install -g bower
- - npm install
- - bower install
+- git clone git://github.com/n1k0/casperjs.git ~/casperjs
+- export PATH=$PATH:~/casperjs/bin
+- npm install -g grunt-cli
+- npm install
before_script:
- - phantomjs --version
- - casperjs --version
+- grunt install
script:
- - grunt
+- sh ./tests/travis.sh
+after_script:
+- cat ./tests/coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
+env:
+ global:
+ - secure: mGHp1rQI11OvbBQn3PnBT5kuyo26gFl8U+nNq0Ot4opgSBX9JaHqS8Dx63uALWWU9qjy08/Mn68t/sKhayH1+XrPDIenOy/XEkkSAG60qAAowD9dRo3WaIMSOcWWYDeqdZOAWZ3LiXvjLO4Swagz5ejz7UtY/ws4CcTi2n/fp7c=
+ - secure: Eao+hPFWKrHb7qUGEzLg7zdTCE//gb3arf5UmI9Z3i+DydSu/AwExXuywJYUj4/JNm/z8zyJ3j1/mdTyyt9VVyrnQNnyGH1b2oCUHkrs1NLwh5Oe4YcqUYROzoEKdDInvmjVJnIfUEM07htGMGvsLsX4MW2tqVHvD2rOwkn8C9s=
diff --git a/Gruntfile.js b/Gruntfile.js
index 57401d84..ecd819c8 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,138 +1,163 @@
/* global module */
-
module.exports = function(grunt) {
'use strict';
grunt.initConfig({
- exec: {
- casperjs : {
- command : 'casperjs test tests/e2e/'
+ pkg: grunt.file.readJSON('package.json'),
+ meta: {
+ banner: '/*!\n' +
+ ' * AngularFire is the officially supported AngularJS binding for Firebase. Firebase\n' +
+ ' * is a full backend so you don\'t need servers to build your Angular app. AngularFire\n' +
+ ' * provides you with the $firebase service which allows you to easily keep your $scope\n' +
+ ' * variables in sync with your Firebase backend.\n' +
+ ' *\n' +
+ ' * AngularFire 0.0.0\n' +
+ ' * https://github.com/firebase/angularfire/\n' +
+ ' * Date: <%= grunt.template.today("mm/dd/yyyy") %>\n' +
+ ' * License: MIT\n' +
+ ' */\n'
+ },
+
+ // merge files from src/ into angularfire.js
+ concat: {
+ app: {
+ options: { banner: '<%= meta.banner %>' },
+ src: [
+ 'src/module.js',
+ 'src/**/*.js'
+ ],
+ dest: 'dist/angularfire.js'
+ }
+ },
+
+ // Run shell commands
+ shell: {
+ options: {
+ stdout: true
+ },
+ protractor_install: {
+ command: 'node ./node_modules/protractor/bin/webdriver-manager update'
+ },
+ npm_install: {
+ command: 'npm install'
+ },
+ bower_install: {
+ command: 'bower install'
+ }
+ },
+
+ // Create local server
+ connect: {
+ testserver: {
+ options: {
+ hostname: 'localhost',
+ port: 3030
+ }
}
},
+ // Minify JavaScript
uglify : {
+ options: {
+ preserveComments: 'some'
+ },
app : {
files : {
- 'angularfire.min.js' : ['angularfire.js']
+ 'dist/angularfire.min.js' : ['dist/angularfire.js']
}
}
},
+ // Lint JavaScript
jshint : {
options : {
- 'bitwise' : true,
- 'boss' : true,
- 'browser' : true,
- 'curly' : true,
- 'devel' : true,
- 'eqnull' : true,
- 'globals' : {
- 'angular' : false,
- 'Firebase' : false,
- 'FirebaseSimpleLogin' : false
- },
- 'globalstrict' : true,
- 'indent' : 2,
- 'latedef' : true,
- 'maxlen' : 115,
- 'noempty' : true,
- 'nonstandard' : true,
- 'undef' : true,
- 'unused' : true,
- 'trailing' : true
+ jshintrc: '.jshintrc',
+ ignores: ['src/lib/polyfills.js']
},
- all : ['angularfire.js']
+ all : ['src/**/*.js']
},
+ // Auto-run tasks on file changes
watch : {
scripts : {
- files : 'angularfire.js',
- tasks : ['default', 'notify:watch'],
+ files : ['src/**/*.js', 'tests/unit/**/*.spec.js', 'tests/lib/**/*.js', 'tests/mocks/**/*.js'],
+ tasks : ['test:unit', 'notify:watch'],
options : {
- interrupt : true
- }
- }
- },
-
- notify: {
- watch: {
- options: {
- title: 'Grunt Watch',
- message: 'Build Finished'
+ interrupt : true,
+ atBegin: true
}
}
},
+ // Unit tests
karma: {
- unit: {
+ options: {
configFile: 'tests/automatic_karma.conf.js'
},
- continuous: {
- configFile: 'tests/automatic_karma.conf.js',
- singleRun: true,
- browsers: ['PhantomJS']
- },
- auto: {
- configFile: 'tests/automatic_karma.conf.js',
- autowatch: true,
- browsers: ['PhantomJS']
- }/*,
- "kato": {
- configFile: 'tests/automatic_karma.conf.js',
- options: {
- files: [
- '../bower_components/angular/angular.js',
- '../bower_components/angular-mocks/angular-mocks.js',
- '../lib/omnibinder-protocol.js',
- 'lib/lodash.js',
- 'lib/MockFirebase.js',
- '../angularfire.js',
- 'unit/AngularFire.spec.js'
- ]
- },
+ singlerun: {},
+ watch: {
autowatch: true,
- browsers: ['PhantomJS']
- }*/
+ singleRun: false
+ },
+ saucelabs: {
+ configFile: 'tests/sauce_karma.conf.js'
+ }
},
- changelog: {
+ // End to end (e2e) tests
+ protractor: {
options: {
- dest: 'CHANGELOG.md'
+ configFile: "tests/local_protractor.conf.js"
+ },
+ singlerun: {},
+ saucelabs: {
+ options: {
+ configFile: "tests/sauce_protractor.conf.js",
+ args: {
+ sauceUser: process.env.SAUCE_USERNAME,
+ sauceKey: process.env.SAUCE_ACCESS_KEY
+ }
+ }
+ }
+ },
+
+ // Desktop notificaitons
+ notify: {
+ watch: {
+ options: {
+ title: 'Grunt Watch',
+ message: 'Build Finished'
+ }
}
}
});
require('load-grunt-tasks')(grunt);
- grunt.registerTask('build', ['jshint', 'uglify']);
- grunt.registerTask('test', ['exec:casperjs', 'karma:continuous']);
+ // Installation
+ grunt.registerTask('install', ['shell:protractor_install']);
+ grunt.registerTask('update', ['shell:npm_install']);
- grunt.registerTask('protractor', 'e2e tests for omnibinder', function () {
- var done = this.async();
+ // Single run tests
+ grunt.registerTask('test', ['test:unit', 'test:e2e']);
+ grunt.registerTask('test:unit', ['karma:singlerun']);
+ grunt.registerTask('test:e2e', ['concat', 'connect:testserver', 'protractor:singlerun']);
- if (!grunt.file.isDir('selenium')) {
- grunt.log.writeln('Installing selenium and chromedriver dependency');
- grunt.util.spawn({
- cmd: './node_modules/protractor/bin/install_selenium_standalone'
- }, function (err) {
- if (err) grunt.log.error(err);
+ // Travis CI testing
+ //grunt.registerTask('test:travis', ['build', 'test:unit', 'connect:testserver', 'protractor:saucelabs']);
+ grunt.registerTask('test:travis', ['build', 'test:unit']);
- runProtractor();
- });
- } else {
- runProtractor();
- }
+ // Sauce tasks
+ grunt.registerTask('sauce:unit', ['karma:saucelabs']);
+ grunt.registerTask('sauce:e2e', ['concat', 'connect:testserver', 'protractor:saucelabs']);
- function runProtractor() {
- grunt.util.spawn({
- cmd: './node_modules/protractor/bin/protractor',
- args: ['tests/protractorConf.js']
- }, function (err, result, code) {
- grunt.log.write(result);
- done(err);
- });
- }
- });
+ // Watch tests
+ grunt.registerTask('test:watch', ['karma:watch']);
+ grunt.registerTask('test:watch:unit', ['karma:watch']);
+
+ // Build tasks
+ grunt.registerTask('build', ['concat', 'jshint', 'uglify']);
+ // Default task
grunt.registerTask('default', ['build', 'test']);
};
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..e2d638cb
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Firebase
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index ceb228eb..b04bf072 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +1,107 @@
-AngularFire
-===========
-AngularFire is an officially supported [AngularJS](http://angularjs.org/) binding
-for [Firebase](http://www.firebase.com/?utm_medium=web&utm_source=angularFire).
-Firebase is a full backend so you don't need servers to build your Angular app!
+# AngularFire _(for AngularJS)_ [](https://travis-ci.org/firebase/angularfire) [](https://coveralls.io/github/firebase/angularfire?branch=master) [](http://badge.fury.io/gh/firebase%2Fangularfire)
-*Please visit the
-[Firebase + Angular Quickstart guide](https://www.firebase.com/quickstart/angularjs.html)
-for more information*.
+**⚠️ Looking for the new AngularFire?** If you're using Angular you'll want to check out [@angular/fire](https://github.com/angular/angularfire).
-We also have a [tutorial](https://www.firebase.com/tutorial/#tutorial/angular/0),
-[documentation](https://www.firebase.com/docs/angular/index.html) and an
-[API reference](https://www.firebase.com/docs/angular/reference.html).
+## Status
-Join our [Firebase + Angular Google Group](https://groups.google.com/forum/#!forum/firebase-angular) to ask questions, provide feedback, and share apps you've built with Firebase and Angular.
+
-Development
------------
-[](https://travis-ci.org/firebase/angularFire)
-[](http://badge.fury.io/bo/angularfire)
-[](http://gruntjs.com/)
+This repository is no longer under active development. No new features will be added and issues are not actively triaged. Pull Requests which fix bugs are welcome and will be reviewed on a best-effort basis.
-If you'd like to hack on AngularFire itself, you'll need
-[node.js](http://nodejs.org/download/), [Bower](http://bower.io), and
-[CasperJS](https://github.com/n1k0/casperjs):
+If you maintain a fork of this repository that you believe is healthier than the official version, we may consider recommending your fork. Please open a Pull Request if you believe that is the case.
-```bash
-npm install -g phantomjs casperjs
-npm install
-bower install
+[AngularJS will be in LTS until December 31st, 2021](https://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c) after which this library will be deprecated.
+
+----
+
+AngularFire is the officially supported [AngularJS](https://angularjs.org/) binding for
+[Firebase](https://firebase.google.com/). Firebase is a backend service that provides data storage,
+file storage, authentication, and static website hosting for your Angular app.
+
+AngularFire is a complement to the core Firebase client. It provides you with several Angular
+services:
+ * `$firebaseObject` - synchronized objects
+ * `$firebaseArray` - synchronized collections
+ * `$firebaseStorage` - store and retrieve user-generated content like images, audio, and video
+ * `$firebaseAuth` - authentication, user management, routing
+
+Join our [Firebase Google Group](https://groups.google.com/forum/#!forum/firebase-talk)
+to ask questions, provide feedback, and share apps you've built with AngularFire.
+
+## Table of Contents
+
+ * [Getting Started With Firebase](#getting-started-with-firebase)
+ * [Downloading AngularFire](#downloading-angularfire)
+ * [Documentation](#documentation)
+ * [Examples](#examples)
+ * [Migration Guides](#migration-guides)
+ * [Contributing](#contributing)
+
+
+## Getting Started With Firebase
+
+AngularFire requires [Firebase](https://firebase.google.com/) in order to authenticate users and sync
+and store data. Firebase is a suite of integrated products designed to help you develop your app,
+grow your user base, and earn money. You can [sign up here for a free account](https://console.firebase.google.com/).
+
+
+## Downloading AngularFire
+
+In order to use AngularFire in your project, you need to include the following files in your HTML:
+
+```html
+
+
+
+
+
+
+
+
```
-Use grunt to build and test the code:
+You can also install AngularFire via npm and Bower and its dependencies will be downloaded
+automatically:
```bash
-# Default task - validates with jshint, minifies source and then runs unit tests
-grunt
+$ npm install angularfire --save
+```
-# Watch for changes and run unit test after each change
-grunt watch
+```bash
+$ bower install angularfire --save
+```
-# Run tests
-grunt test
-# Minify source
-grunt build
-```
+## Documentation
+
+* [Quickstart](docs/quickstart.md)
+* [Guide](docs/guide/README.md)
+* [API Reference](docs/reference.md)
+
+
+## Examples
+
+### Full Examples
+
+* [Wait And Eat](https://github.com/gordonmzhu/angular-course-demo-app-v2)
+* [TodoMVC](https://github.com/tastejs/todomvc/tree/master/examples/firebase-angular)
+* [Tic-Tac-Tic-Tac-Toe](https://github.com/jwngr/tic-tac-tic-tac-toe/)
+* [Firereader](http://github.com/firebase/firereader)
+* [Firepoker](https://github.com/Wizehive/Firepoker)
+
+### Recipes
+
+* [Date Object To A Firebase Timestamp Using `$extend`](http://jsfiddle.net/katowulf/syuzw9k1/)
+* [Filter a `$FirebaseArray`](http://jsfiddle.net/firebase/ku8uL0pr/)
+
+
+## Migration Guides
+
+* [Migrating from AngularFire `1.x.x` to `2.x.x`](docs/migration/1XX-to-2XX.md)
+* [Migrating from AngularFire `0.9.x` to `1.x.x`](docs/migration/09X-to-1XX.md)
+
+
+## Contributing
-License
--------
-[MIT](http://firebase.mit-license.org).
+If you'd like to contribute to AngularFire, please first read through our [contribution
+guidelines](.github/CONTRIBUTING.md). Local setup instructions are available [here](.github/CONTRIBUTING.md#local-setup).
diff --git a/angularfire.js b/angularfire.js
deleted file mode 100644
index 73deeede..00000000
--- a/angularfire.js
+++ /dev/null
@@ -1,1012 +0,0 @@
-// AngularFire is an officially supported AngularJS binding for Firebase.
-// The bindings let you associate a Firebase URL with a model (or set of
-// models), and they will be transparently kept in sync across all clients
-// currently using your app. The 2-way data binding offered by AngularJS works
-// as normal, except that the changes are also sent to all other clients
-// instead of just a server.
-//
-// AngularFire 0.7.1-pre2
-// http://angularfire.com
-// License: MIT
-
-"use strict";
-
-(function() {
-
- var AngularFire, AngularFireAuth;
-
- // Define the `firebase` module under which all AngularFire
- // services will live.
- angular.module("firebase", []).value("Firebase", Firebase);
-
- // Define the `$firebase` service that provides synchronization methods.
- angular.module("firebase").factory("$firebase", ["$q", "$parse", "$timeout",
- function($q, $parse, $timeout) {
- // The factory returns an object containing the value of the data at
- // the Firebase location provided, as well as several methods. It
- // takes a single argument:
- //
- // * `ref`: A Firebase reference. Queries or limits may be applied.
- return function(ref) {
- var af = new AngularFire($q, $parse, $timeout, ref);
- return af.construct();
- };
- }
- ]);
-
- // Define the `orderByPriority` filter that sorts objects returned by
- // $firebase in the order of priority. Priority is defined by Firebase,
- // for more info see: https://www.firebase.com/docs/ordered-data.html
- angular.module("firebase").filter("orderByPriority", function() {
- return function(input) {
- var sorted = [];
- if (input) {
- if (!input.$getIndex || typeof input.$getIndex != "function") {
- // input is not an angularFire instance
- if (angular.isArray(input)) {
- // If input is an array, copy it
- sorted = input.slice(0);
- } else if (angular.isObject(input)) {
- // If input is an object, map it to an array
- angular.forEach(input, function(prop) {
- sorted.push(prop);
- });
- }
- } else {
- // input is an angularFire instance
- var index = input.$getIndex();
- if (index.length > 0) {
- for (var i = 0; i < index.length; i++) {
- var val = input[index[i]];
- if (val) {
- val.$id = index[i];
- sorted.push(val);
- }
- }
- }
- }
- }
- return sorted;
- };
- });
-
- // Shim Array.indexOf for IE compatibility.
- if (!Array.prototype.indexOf) {
- Array.prototype.indexOf = function (searchElement, fromIndex) {
- if (this === undefined || this === null) {
- throw new TypeError("'this' is null or not defined");
- }
- // Hack to convert object.length to a UInt32
- // jshint -W016
- var length = this.length >>> 0;
- fromIndex = +fromIndex || 0;
- // jshint +W016
-
- if (Math.abs(fromIndex) === Infinity) {
- fromIndex = 0;
- }
-
- if (fromIndex < 0) {
- fromIndex += length;
- if (fromIndex < 0) {
- fromIndex = 0;
- }
- }
-
- for (;fromIndex < length; fromIndex++) {
- if (this[fromIndex] === searchElement) {
- return fromIndex;
- }
- }
-
- return -1;
- };
- }
-
- // The `AngularFire` object that implements synchronization.
- AngularFire = function($q, $parse, $timeout, ref) {
- this._q = $q;
- this._bound = false;
- this._loaded = false;
- this._parse = $parse;
- this._timeout = $timeout;
-
- this._index = [];
-
- // An object storing handlers used for different events.
- this._on = {
- value: [],
- change: [],
- loaded: [],
- child_added: [],
- child_moved: [],
- child_changed: [],
- child_removed: []
- };
-
- if (typeof ref == "string") {
- throw new Error("Please provide a Firebase reference instead " +
- "of a URL, eg: new Firebase(url)");
- }
- this._fRef = ref;
- };
-
- AngularFire.prototype = {
- // This function is called by the factory to create a new explicit sync
- // point between a particular model and a Firebase location.
- construct: function() {
- var self = this;
- var object = {};
-
- // Set the $id val equal to the Firebase reference's name() function.
- object.$id = self._fRef.ref().name();
-
- // Establish a 3-way data binding (implicit sync) with the specified
- // Firebase location and a model on $scope. To be used from a controller
- // to automatically synchronize *all* local changes. It takes three
- // arguments:
- //
- // * `$scope` : The scope with which the bound model is associated.
- // * `name` : The name of the model.
- // * `defaultFn`: A function that provides a default value if the
- // remote value is not set. Optional.
- //
- // This function also returns a promise, which, when resolved, will be
- // provided an `unbind` method, a function which you can call to stop
- // watching the local model for changes.
- object.$bind = function(scope, name, defaultFn) {
- return self._bind(scope, name, defaultFn);
- };
-
- // Add an object to the remote data. Adding an object is the
- // equivalent of calling `push()` on a Firebase reference. It takes
- // one argument:
- //
- // * `item`: The object or primitive to add.
- //
- // This function returns a promise that will be resolved when the data
- // has been successfully written to the server. If the promise is
- // resolved, it will be provided with a reference to the newly added
- // object or primitive. The key name can be extracted using `ref.name()`.
- // If the promise fails, it will resolve to an error.
- object.$add = function(item) {
- var ref;
- var deferred = self._q.defer();
-
- function _addCb(err) {
- if (err) {
- deferred.reject(err);
- } else {
- deferred.resolve(ref);
- }
- }
-
- if (typeof item == "object") {
- ref = self._fRef.ref().push(self._parseObject(item), _addCb);
- } else {
- ref = self._fRef.ref().push(item, _addCb);
- }
-
- return deferred.promise;
- };
-
- // Save the current state of the object (or a child) to the remote.
- // Takes a single optional argument:
- //
- // * `key`: Specify a child key to save the data for. If no key is
- // specified, the entire object's current state will
- // be saved.
- //
- // This function returns a promise that will be resolved when the
- // data has been successfully saved to the server.
- object.$save = function(key) {
- var deferred = self._q.defer();
-
- function _saveCb(err) {
- if (err) {
- deferred.reject(err);
- } else {
- deferred.resolve();
- }
- }
-
- if (key) {
- var obj = self._parseObject(self._object[key]);
- self._fRef.ref().child(key).set(obj, _saveCb);
- } else {
- self._fRef.ref().set(self._parseObject(self._object), _saveCb);
- }
-
- return deferred.promise;
- };
-
- // Set the current state of the object to the specified value. Calling
- // this is the equivalent of calling `set()` on a Firebase reference.
- // Takes a single mandatory argument:
- //
- // * `newValue`: The value which should overwrite data stored at
- // this location.
- //
- // This function returns a promise that will be resolved when the
- // data has been successfully saved to the server.
- object.$set = function(newValue) {
- var deferred = self._q.defer();
- self._fRef.ref().set(self._parseObject(newValue), function(err) {
- if (err) {
- deferred.reject(err);
- } else {
- deferred.resolve();
- }
- });
- return deferred.promise;
- };
-
- // Non-destructively update only a subset of keys for the current object.
- // This is the equivalent of calling `update()` on a Firebase reference.
- // Takes a single mandatory argument:
- //
- // * `newValue`: The set of keys and values that must be updated for
- // this location.
- //
- // This function returns a promise that will be resolved when the data
- // has been successfully saved to the server.
- object.$update = function(newValue) {
- var deferred = self._q.defer();
- self._fRef.ref().update(self._parseObject(newValue), function(err) {
- if (err) {
- deferred.reject(err);
- } else {
- deferred.resolve();
- }
- });
- return deferred.promise;
- };
-
- // Update a value within a transaction. Calling this is the
- // equivalent of calling `transaction()` on a Firebase reference.
- //
- // * `updateFn`: A developer-supplied function which will be passed
- // the current data stored at this location (as a
- // Javascript object). The function should return the
- // new value it would like written (as a Javascript
- // object). If "undefined" is returned (i.e. you
- // "return;" with no arguments) the transaction will
- // be aborted and the data at this location will not
- // be modified.
- // * `applyLocally`: By default, events are raised each time the
- // transaction update function runs. So if it is run
- // multiple times, you may see intermediate states.
- // You can set this to false to suppress these
- // intermediate states and instead wait until the
- // transaction has completed before events are raised.
- //
- // This function returns a promise that will be resolved when the
- // transaction function has completed. A successful transaction is
- // resolved with the snapshot. If the transaction is aborted,
- // the promise will be resolved with null.
- object.$transaction = function(updateFn, applyLocally) {
- var deferred = self._q.defer();
- self._fRef.ref().transaction(updateFn,
- function(err, committed, snapshot) {
- if (err) {
- deferred.reject(err);
- } else if (!committed) {
- deferred.resolve(null);
- } else {
- deferred.resolve(snapshot);
- }
- },
- applyLocally);
-
- return deferred.promise;
- };
-
- // Remove this object from the remote data. Calling this is the
- // equivalent of calling `remove()` on a Firebase reference. This
- // function takes a single optional argument:
- //
- // * `key`: Specify a child key to remove. If no key is specified, the
- // entire object will be removed from the remote data store.
- //
- // This function returns a promise that will be resolved when the
- // object has been successfully removed from the server.
- object.$remove = function(key) {
- var deferred = self._q.defer();
-
- function _removeCb(err) {
- if (err) {
- deferred.reject(err);
- } else {
- deferred.resolve();
- }
- }
-
- if (key) {
- self._fRef.ref().child(key).remove(_removeCb);
- } else {
- self._fRef.ref().remove(_removeCb);
- }
-
- return deferred.promise;
- };
-
- // Get an AngularFire wrapper for a named child. This function takes
- // one mandatory argument:
- //
- // * `key`: The key name that will point to the child reference to be
- // returned.
- object.$child = function(key) {
- var af = new AngularFire(
- self._q, self._parse, self._timeout, self._fRef.ref().child(key)
- );
- return af.construct();
- };
-
- // Attach an event handler for when the object is changed. You can attach
- // handlers for all Firebase events like "child_added", "value", and
- // "child_removed". Additionally, the following events, specific to
- // AngularFire, can be listened to.
- //
- // - "change": The provided function will be called whenever the local
- // object is modified because the remote data was updated.
- // - "loaded": This function will be called *once*, when the initial
- // data has been loaded. 'object' will be an empty
- // object ({}) until this function is called.
- object.$on = function(type, callback) {
- if( self._on.hasOwnProperty(type) ) {
- self._sendInitEvent(type, callback);
- // One exception if made for the 'loaded' event. If we already loaded
- // data (perhaps because it was synced), simply fire the callback.
- if (type !== "loaded" || !this._loaded) {
- self._on[type].push(callback);
- }
- } else {
- throw new Error("Invalid event type " + type + " specified");
- }
- return object;
- };
-
- // Detach an event handler from a specified event type. If no callback
- // is specified, all event handlers for the specified event type will
- // be detached.
- //
- // If no type if provided, synchronization for this instance of $firebase
- // will be turned off complete.
- object.$off = function(type, callback) {
- if (self._on.hasOwnProperty(type)) {
- if (callback) {
- var index = self._on[type].indexOf(callback);
- if (index !== -1) {
- self._on[type].splice(index, 1);
- }
- } else {
- self._on[type] = [];
- }
- } else {
- self._fRef.off();
- }
- };
-
- // Authenticate this Firebase reference with a custom auth token.
- // Refer to the Firebase documentation on "Custom Login" for details.
- // Returns a promise that will be resolved when authentication is
- // successfully completed.
- object.$auth = function(token) {
- var deferred = self._q.defer();
- self._fRef.auth(token, function(err, obj) {
- if (err !== null) {
- deferred.reject(err);
- } else {
- deferred.resolve(obj);
- }
- }, function(rej) {
- deferred.reject(rej);
- });
- return deferred.promise;
- };
-
- // Return the current index, which is a list of key names in an array,
- // ordered by their Firebase priority.
- object.$getIndex = function() {
- return angular.copy(self._index);
- };
-
- // Return the reference used by this object.
- object.$getRef = function() {
- return self._fRef.ref();
- };
-
- self._object = object;
- self._getInitialValue();
-
- return self._object;
- },
-
- // This function is responsible for fetching the initial data for the
- // given reference. If the data returned from the server is an object or
- // array, we'll attach appropriate child event handlers. If the value is
- // a primitive, we'll continue to watch for value changes.
- _getInitialValue: function() {
- var self = this;
- var gotInitialValue = function(snapshot) {
- var value = snapshot.val();
- if (value === null) {
- // NULLs are handled specially. If there's a 3-way data binding
- // on a local primitive, then update that, otherwise switch to object
- // binding using child events.
- if (self._bound) {
- var local = self._parseObject(self._parse(self._name)(self._scope));
- switch (typeof local) {
- // Primitive defaults.
- case "string":
- case "undefined":
- value = "";
- break;
- case "number":
- value = 0;
- break;
- case "boolean":
- value = false;
- break;
- }
- }
- }
-
- // Call handlers for the "loaded" event.
- if (self._loaded !== true) {
- self._loaded = true;
- self._broadcastEvent("loaded", value);
- if( self._on.hasOwnProperty('child_added')) {
- self._iterateChildren(function(key, val, prevChild) {
- self._broadcastEvent('child_added', self._makeEventSnapshot(key, val, prevChild));
- });
- }
- }
-
- self._broadcastEvent('value', self._makeEventSnapshot(snapshot.name(), value, null));
-
- switch (typeof value) {
- // For primitive values, simply update the object returned.
- case "string":
- case "number":
- case "boolean":
- self._updatePrimitive(value);
- break;
- // For arrays and objects, switch to child methods.
- case "object":
- self._fRef.off("value", gotInitialValue);
- // Before switching to child methods, save priority for top node.
- if (snapshot.getPriority() !== null) {
- self._updateModel("$priority", snapshot.getPriority());
- }
- self._getChildValues();
- break;
- default:
- throw new Error("Unexpected type from remote data " + typeof value);
- }
- };
-
- self._fRef.on("value", gotInitialValue);
- },
-
- // This function attaches child events for object and array types.
- _getChildValues: function() {
- var self = this;
- // Store the priority of the current property as "$priority". Changing
- // the value of this property will also update the priority of the
- // object (see _parseObject).
- function _processSnapshot(snapshot, prevChild) {
- var key = snapshot.name();
- var val = snapshot.val();
-
- // If the item already exists in the index, remove it first.
- var curIdx = self._index.indexOf(key);
- if (curIdx !== -1) {
- self._index.splice(curIdx, 1);
- }
-
- // Update index. This is used by $getIndex and orderByPriority.
- if (prevChild) {
- var prevIdx = self._index.indexOf(prevChild);
- self._index.splice(prevIdx + 1, 0, key);
- } else {
- self._index.unshift(key);
- }
-
- // Update local model with priority field, if needed.
- if (snapshot.getPriority() !== null) {
- val.$priority = snapshot.getPriority();
- }
- self._updateModel(key, val);
- }
-
- // Helper function to attach and broadcast events.
- function _handleAndBroadcastEvent(type, handler) {
- return function(snapshot, prevChild) {
- handler(snapshot, prevChild);
- self._broadcastEvent(type, self._makeEventSnapshot(snapshot.name(), snapshot.val(), prevChild));
- };
- }
-
- function _handleFirebaseEvent(type, handler) {
- self._fRef.on(type, _handleAndBroadcastEvent(type, handler));
- }
- _handleFirebaseEvent("child_added", _processSnapshot);
- _handleFirebaseEvent("child_moved", _processSnapshot);
- _handleFirebaseEvent("child_changed", _processSnapshot);
- _handleFirebaseEvent("child_removed", function(snapshot) {
- // Remove from index.
- var key = snapshot.name();
- var idx = self._index.indexOf(key);
- self._index.splice(idx, 1);
-
- // Remove from local model.
- self._updateModel(key, null);
- });
- self._fRef.on('value', function(snap) {
- self._broadcastEvent('value', self._makeEventSnapshot(snap.name(), snap.val()));
- });
- },
-
- // Called whenever there is a remote change. Applies them to the local
- // model for both explicit and implicit sync modes.
- _updateModel: function(key, value) {
- var self = this;
- self._timeout(function() {
- if (value == null) {
- delete self._object[key];
- } else {
- self._object[key] = value;
- }
-
- // Call change handlers.
- self._broadcastEvent("change", key);
-
- // If there is an implicit binding, also update the local model.
- if (!self._bound) {
- return;
- }
-
- var current = self._object;
- var local = self._parse(self._name)(self._scope);
- // If remote value matches local value, don't do anything, otherwise
- // apply the change.
- if (!angular.equals(current, local)) {
- self._parse(self._name).assign(self._scope, angular.copy(current));
- }
- });
- },
-
- // Called whenever there is a remote change for a primitive value.
- _updatePrimitive: function(value) {
- var self = this;
- self._timeout(function() {
- // Primitive values are represented as a special object
- // {$value: value}. Only update if the remote value is different from
- // the local value.
- if (!self._object.$value ||
- !angular.equals(self._object.$value, value)) {
- self._object.$value = value;
- }
-
- // Call change handlers.
- self._broadcastEvent("change");
-
- // If there's an implicit binding, simply update the local scope model.
- if (self._bound) {
- var local = self._parseObject(self._parse(self._name)(self._scope));
- if (!angular.equals(local, value)) {
- self._parse(self._name).assign(self._scope, value);
- }
- }
- });
- },
-
- // If event handlers for a specified event were attached, call them.
- _broadcastEvent: function(evt, param) {
- var cbs = this._on[evt] || [];
- if( evt === 'loaded' ) {
- this._on[evt] = []; // release memory
- }
- var self = this;
-
- function _wrapTimeout(cb, param) {
- self._timeout(function() {
- cb(param);
- });
- }
-
- if (cbs.length > 0) {
- for (var i = 0; i < cbs.length; i++) {
- if (typeof cbs[i] == "function") {
- _wrapTimeout(cbs[i], param);
- }
- }
- }
- },
-
- // triggers an initial event for loaded, value, and child_added events (which get immediate feedback)
- _sendInitEvent: function(evt, callback) {
- var self = this;
- if( self._loaded && ['child_added', 'loaded', 'value'].indexOf(evt) > -1 ) {
- self._timeout(function() {
- var parsedValue = angular.isObject(self._object)? self._parseObject(self._object) : self._object;
- switch(evt) {
- case 'loaded':
- callback(parsedValue);
- break;
- case 'value':
- callback(self._makeEventSnapshot(self._fRef.name(), parsedValue, null));
- break;
- case 'child_added':
- self._iterateChildren(parsedValue, function(name, val, prev) {
- callback(self._makeEventSnapshot(name, val, prev));
- });
- break;
- default: // not reachable
- }
- });
- }
- },
-
- // assuming data is an object, this method will iterate all
- // child keys and invoke callback with (key, value, prevChild)
- _iterateChildren: function(data, callback) {
- if( this._loaded && angular.isObject(data) ) {
- var prev = null;
- for(var key in data) {
- if( data.hasOwnProperty(key) ) {
- callback(key, data[key], prev);
- prev = key;
- }
- }
- }
- },
-
- // creates a snapshot object compatible with _broadcastEvent notifications
- _makeEventSnapshot: function(key, value, prevChild) {
- if( angular.isUndefined(prevChild) ) {
- prevChild = null;
- }
- return {
- snapshot: {
- name: key,
- value: value
- },
- prevChild: prevChild
- };
- },
-
- // This function creates a 3-way binding between the provided scope model
- // and Firebase. All changes made to the local model are saved to Firebase
- // and changes to the remote data automatically appear on the local model.
- _bind: function(scope, name, defaultFn) {
- var self = this;
- var deferred = self._q.defer();
-
- // _updateModel or _updatePrimitive will take care of updating the local
- // model if _bound is set to true.
- self._name = name;
- self._bound = true;
- self._scope = scope;
-
- // If the local model is an object, call an update to set local values.
- var local = self._parse(name)(scope);
- if (local !== undefined && typeof local == "object") {
- self._fRef.ref().update(self._parseObject(local));
- }
-
- // We're responsible for setting up scope.$watch to reflect local changes
- // on the Firebase data.
- var unbind = scope.$watch(name, function() {
- // If the new local value matches the current remote value, we don't
- // trigger a remote update.
- var local = self._parseObject(self._parse(name)(scope));
- if (self._object.$value !== undefined &&
- angular.equals(local, self._object.$value)) {
- return;
- } else if (angular.equals(local, self._parseObject(self._object))) {
- return;
- }
-
- // If the local model is undefined or the remote data hasn't been
- // loaded yet, don't update.
- if (local === undefined || !self._loaded) {
- return;
- }
-
- // Use update if limits are in effect, set if not.
- if (self._fRef.set) {
- self._fRef.set(local);
- } else {
- self._fRef.ref().update(local);
- }
- }, true);
-
- // When the scope is destroyed, unbind automatically.
- scope.$on("$destroy", function() {
- unbind();
- });
-
- // Once we receive the initial value, the promise will be resolved.
- self._fRef.once("value", function(snap) {
- self._timeout(function() {
- // HACK / FIXME: Objects require a second event loop run, since we
- // switch from value events to child_added. See #209 on Github.
- if (typeof snap.val() != "object") {
- // If the remote value is not set and defaultFn was provided,
- // initialize the local value with the result of defaultFn().
- if (snap.val() == null && typeof defaultFn === 'function') {
- scope[name] = defaultFn();
- }
- deferred.resolve(unbind);
- } else {
- self._timeout(function() {
- // If the remote value is not set and defaultFn was provided,
- // initialize the local value with the result of defaultFn().
- if (snap.val() == null && typeof defaultFn === 'function') {
- scope[name] = defaultFn();
- }
- deferred.resolve(unbind);
- });
- }
- });
- });
-
- return deferred.promise;
- },
-
- // Parse a local model, removing all properties beginning with "$" and
- // converting $priority to ".priority".
- _parseObject: function(obj) {
- function _findReplacePriority(item) {
- for (var prop in item) {
- if (item.hasOwnProperty(prop)) {
- if (prop == "$priority") {
- item[".priority"] = item.$priority;
- delete item.$priority;
- } else if (typeof item[prop] == "object") {
- _findReplacePriority(item[prop]);
- }
- }
- }
- return item;
- }
-
- // We use toJson/fromJson to remove $$hashKey and others. Can be replaced
- // by angular.copy, but only for later versions of AngularJS.
- var newObj = _findReplacePriority(angular.copy(obj));
- return angular.fromJson(angular.toJson(newObj));
- }
- };
-
-
- // Defines the `$firebaseSimpleLogin` service that provides simple
- // user authentication support for AngularFire.
- angular.module("firebase").factory("$firebaseSimpleLogin", [
- "$q", "$timeout", "$rootScope", function($q, $t, $rs) {
- // The factory returns an object containing the authentication state
- // of the current user. This service takes one argument:
- //
- // * `ref` : A Firebase reference.
- //
- // The returned object has the following properties:
- //
- // * `user`: Set to "null" if the user is currently logged out. This
- // value will be changed to an object when the user successfully logs
- // in. This object will contain details of the logged in user. The
- // exact properties will vary based on the method used to login, but
- // will at a minimum contain the `id` and `provider` properties.
- //
- // The returned object will also have the following methods available:
- // $login(), $logout(), $createUser(), $changePassword(), $removeUser(),
- // and $getCurrentUser().
- return function(ref) {
- var auth = new AngularFireAuth($q, $t, $rs, ref);
- return auth.construct();
- };
- }
- ]);
-
- AngularFireAuth = function($q, $t, $rs, ref) {
- this._q = $q;
- this._timeout = $t;
- this._rootScope = $rs;
- this._loginDeferred = null;
- this._getCurrentUserDeferred = [];
- this._currentUserData = undefined;
-
- if (typeof ref == "string") {
- throw new Error("Please provide a Firebase reference instead " +
- "of a URL, eg: new Firebase(url)");
- }
- this._fRef = ref;
- };
-
- AngularFireAuth.prototype = {
- construct: function() {
- var object = {
- user: null,
- $login: this.login.bind(this),
- $logout: this.logout.bind(this),
- $createUser: this.createUser.bind(this),
- $changePassword: this.changePassword.bind(this),
- $removeUser: this.removeUser.bind(this),
- $getCurrentUser: this.getCurrentUser.bind(this),
- $sendPasswordResetEmail: this.sendPasswordResetEmail.bind(this)
- };
- this._object = object;
-
- // Initialize Simple Login.
- if (!window.FirebaseSimpleLogin) {
- var err = new Error("FirebaseSimpleLogin is undefined. " +
- "Did you forget to include firebase-simple-login.js?");
- this._rootScope.$broadcast("$firebaseSimpleLogin:error", err);
- throw err;
- }
-
- var client = new FirebaseSimpleLogin(this._fRef,
- this._onLoginEvent.bind(this));
- this._authClient = client;
- return this._object;
- },
-
- // The login method takes a provider (for Simple Login) and authenticates
- // the Firebase reference with which the service was initialized. This
- // method returns a promise, which will be resolved when the login succeeds
- // (and rejected when an error occurs).
- login: function(provider, options) {
- var deferred = this._q.defer();
- var self = this;
-
- // To avoid the promise from being fulfilled by our initial login state,
- // make sure we have it before triggering the login and creating a new
- // promise.
- this.getCurrentUser().then(function() {
- self._loginDeferred = deferred;
- self._authClient.login(provider, options);
- });
-
- return deferred.promise;
- },
-
- // Unauthenticate the Firebase reference.
- logout: function() {
- // Tell the simple login client to log us out.
- this._authClient.logout();
-
- // Forget who we were, so that any getCurrentUser calls will wait for
- // another user event.
- delete this._currentUserData;
- },
-
- // Creates a user for Firebase Simple Login. Function 'cb' receives an
- // error as the first argument and a Simple Login user object as the second
- // argument. Note that this function only creates the user, if you wish to
- // log in as the newly created user, call $login() after the promise for
- // this method has been fulfilled.
- createUser: function(email, password) {
- var self = this;
- var deferred = this._q.defer();
-
- self._authClient.createUser(email, password, function(err, user) {
- if (err) {
- self._rootScope.$broadcast("$firebaseSimpleLogin:error", err);
- deferred.reject(err);
- } else {
- deferred.resolve(user);
- }
- });
-
- return deferred.promise;
- },
-
- // Changes the password for a Firebase Simple Login user. Take an email,
- // old password and new password as three mandatory arguments. Returns a
- // promise.
- changePassword: function(email, oldPassword, newPassword) {
- var self = this;
- var deferred = this._q.defer();
-
- self._authClient.changePassword(email, oldPassword, newPassword,
- function(err) {
- if (err) {
- self._rootScope.$broadcast("$firebaseSimpleLogin:error", err);
- deferred.reject(err);
- } else {
- deferred.resolve();
- }
- }
- );
-
- return deferred.promise;
- },
-
- // Gets a promise for the current user info.
- getCurrentUser: function() {
- var self = this;
- var deferred = this._q.defer();
-
- if (self._currentUserData !== undefined) {
- deferred.resolve(self._currentUserData);
- } else {
- self._getCurrentUserDeferred.push(deferred);
- }
-
- return deferred.promise;
- },
-
- // Remove a user for the listed email address. Returns a promise.
- removeUser: function(email, password) {
- var self = this;
- var deferred = this._q.defer();
-
- self._authClient.removeUser(email, password, function(err) {
- if (err) {
- self._rootScope.$broadcast("$firebaseSimpleLogin:error", err);
- deferred.reject(err);
- } else {
- deferred.resolve();
- }
- });
-
- return deferred.promise;
- },
-
- // Send a password reset email to the user for an email + password account.
- sendPasswordResetEmail: function(email) {
- var self = this;
- var deferred = this._q.defer();
-
- self._authClient.sendPasswordResetEmail(email, function(err) {
- if (err) {
- self._rootScope.$broadcast("$firebaseSimpleLogin:error", err);
- deferred.reject(err);
- } else {
- deferred.resolve();
- }
- });
-
- return deferred.promise;
- },
-
- // Internal callback for any Simple Login event.
- _onLoginEvent: function(err, user) {
- // HACK -- calls to logout() trigger events even if we're not logged in,
- // making us get extra events. Throw them away. This should be fixed by
- // changing Simple Login so that its callbacks refer directly to the
- // action that caused them.
- if (this._currentUserData === user && err === null) {
- return;
- }
-
- var self = this;
- if (err) {
- if (self._loginDeferred) {
- self._loginDeferred.reject(err);
- self._loginDeferred = null;
- }
- self._rootScope.$broadcast("$firebaseSimpleLogin:error", err);
- } else {
- this._currentUserData = user;
-
- self._timeout(function() {
- self._object.user = user;
- if (user) {
- self._rootScope.$broadcast("$firebaseSimpleLogin:login", user);
- } else {
- self._rootScope.$broadcast("$firebaseSimpleLogin:logout");
- }
- if (self._loginDeferred) {
- self._loginDeferred.resolve(user);
- self._loginDeferred = null;
- }
- while (self._getCurrentUserDeferred.length > 0) {
- var def = self._getCurrentUserDeferred.pop();
- def.resolve(user);
- }
- });
- }
- }
- };
-})();
diff --git a/angularfire.min.js b/angularfire.min.js
deleted file mode 100644
index 41ad72ef..00000000
--- a/angularfire.min.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";!function(){var a,b;angular.module("firebase",[]).value("Firebase",Firebase),angular.module("firebase").factory("$firebase",["$q","$parse","$timeout",function(b,c,d){return function(e){var f=new a(b,c,d,e);return f.construct()}}]),angular.module("firebase").filter("orderByPriority",function(){return function(a){var b=[];if(a)if(a.$getIndex&&"function"==typeof a.$getIndex){var c=a.$getIndex();if(c.length>0)for(var d=0;d>>0;for(b=+b||0,1/0===Math.abs(b)&&(b=0),0>b&&(b+=c,0>b&&(b=0));c>b;b++)if(this[b]===a)return b;return-1}),a=function(a,b,c,d){if(this._q=a,this._bound=!1,this._loaded=!1,this._parse=b,this._timeout=c,this._index=[],this._on={value:[],change:[],loaded:[],child_added:[],child_moved:[],child_changed:[],child_removed:[]},"string"==typeof d)throw new Error("Please provide a Firebase reference instead of a URL, eg: new Firebase(url)");this._fRef=d},a.prototype={construct:function(){var b=this,c={};return c.$id=b._fRef.ref().name(),c.$bind=function(a,c,d){return b._bind(a,c,d)},c.$add=function(a){function c(a){a?e.reject(a):e.resolve(d)}var d,e=b._q.defer();return d="object"==typeof a?b._fRef.ref().push(b._parseObject(a),c):b._fRef.ref().push(a,c),e.promise},c.$save=function(a){function c(a){a?d.reject(a):d.resolve()}var d=b._q.defer();if(a){var e=b._parseObject(b._object[a]);b._fRef.ref().child(a).set(e,c)}else b._fRef.ref().set(b._parseObject(b._object),c);return d.promise},c.$set=function(a){var c=b._q.defer();return b._fRef.ref().set(b._parseObject(a),function(a){a?c.reject(a):c.resolve()}),c.promise},c.$update=function(a){var c=b._q.defer();return b._fRef.ref().update(b._parseObject(a),function(a){a?c.reject(a):c.resolve()}),c.promise},c.$transaction=function(a,c){var d=b._q.defer();return b._fRef.ref().transaction(a,function(a,b,c){a?d.reject(a):d.resolve(b?c:null)},c),d.promise},c.$remove=function(a){function c(a){a?d.reject(a):d.resolve()}var d=b._q.defer();return a?b._fRef.ref().child(a).remove(c):b._fRef.ref().remove(c),d.promise},c.$child=function(c){var d=new a(b._q,b._parse,b._timeout,b._fRef.ref().child(c));return d.construct()},c.$on=function(a,d){if(!b._on.hasOwnProperty(a))throw new Error("Invalid event type "+a+" specified");return b._sendInitEvent(a,d),"loaded"===a&&this._loaded||b._on[a].push(d),c},c.$off=function(a,c){if(b._on.hasOwnProperty(a))if(c){var d=b._on[a].indexOf(c);-1!==d&&b._on[a].splice(d,1)}else b._on[a]=[];else b._fRef.off()},c.$auth=function(a){var c=b._q.defer();return b._fRef.auth(a,function(a,b){null!==a?c.reject(a):c.resolve(b)},function(a){c.reject(a)}),c.promise},c.$getIndex=function(){return angular.copy(b._index)},c.$getRef=function(){return b._fRef.ref()},b._object=c,b._getInitialValue(),b._object},_getInitialValue:function(){var a=this,b=function(c){var d=c.val();if(null===d&&a._bound){var e=a._parseObject(a._parse(a._name)(a._scope));switch(typeof e){case"string":case"undefined":d="";break;case"number":d=0;break;case"boolean":d=!1}}switch(a._loaded!==!0&&(a._loaded=!0,a._broadcastEvent("loaded",d),a._on.hasOwnProperty("child_added")&&a._iterateChildren(function(b,c,d){a._broadcastEvent("child_added",a._makeEventSnapshot(b,c,d))})),a._broadcastEvent("value",a._makeEventSnapshot(c.name(),d,null)),typeof d){case"string":case"number":case"boolean":a._updatePrimitive(d);break;case"object":a._fRef.off("value",b),null!==c.getPriority()&&a._updateModel("$priority",c.getPriority()),a._getChildValues();break;default:throw new Error("Unexpected type from remote data "+typeof d)}};a._fRef.on("value",b)},_getChildValues:function(){function a(a,b){var c=a.name(),e=a.val(),f=d._index.indexOf(c);if(-1!==f&&d._index.splice(f,1),b){var g=d._index.indexOf(b);d._index.splice(g+1,0,c)}else d._index.unshift(c);null!==a.getPriority()&&(e.$priority=a.getPriority()),d._updateModel(c,e)}function b(a,b){return function(c,e){b(c,e),d._broadcastEvent(a,d._makeEventSnapshot(c.name(),c.val(),e))}}function c(a,c){d._fRef.on(a,b(a,c))}var d=this;c("child_added",a),c("child_moved",a),c("child_changed",a),c("child_removed",function(a){var b=a.name(),c=d._index.indexOf(b);d._index.splice(c,1),d._updateModel(b,null)}),d._fRef.on("value",function(a){d._broadcastEvent("value",d._makeEventSnapshot(a.name(),a.val()))})},_updateModel:function(a,b){var c=this;c._timeout(function(){if(null==b?delete c._object[a]:c._object[a]=b,c._broadcastEvent("change",a),c._bound){var d=c._object,e=c._parse(c._name)(c._scope);angular.equals(d,e)||c._parse(c._name).assign(c._scope,angular.copy(d))}})},_updatePrimitive:function(a){var b=this;b._timeout(function(){if(b._object.$value&&angular.equals(b._object.$value,a)||(b._object.$value=a),b._broadcastEvent("change"),b._bound){var c=b._parseObject(b._parse(b._name)(b._scope));angular.equals(c,a)||b._parse(b._name).assign(b._scope,a)}})},_broadcastEvent:function(a,b){function c(a,b){e._timeout(function(){a(b)})}var d=this._on[a]||[];"loaded"===a&&(this._on[a]=[]);var e=this;if(d.length>0)for(var f=0;f-1&&c._timeout(function(){var d=angular.isObject(c._object)?c._parseObject(c._object):c._object;switch(a){case"loaded":b(d);break;case"value":b(c._makeEventSnapshot(c._fRef.name(),d,null));break;case"child_added":c._iterateChildren(d,function(a,d,e){b(c._makeEventSnapshot(a,d,e))})}})},_iterateChildren:function(a,b){if(this._loaded&&angular.isObject(a)){var c=null;for(var d in a)a.hasOwnProperty(d)&&(b(d,a[d],c),c=d)}},_makeEventSnapshot:function(a,b,c){return angular.isUndefined(c)&&(c=null),{snapshot:{name:a,value:b},prevChild:c}},_bind:function(a,b,c){var d=this,e=d._q.defer();d._name=b,d._bound=!0,d._scope=a;var f=d._parse(b)(a);void 0!==f&&"object"==typeof f&&d._fRef.ref().update(d._parseObject(f));var g=a.$watch(b,function(){var c=d._parseObject(d._parse(b)(a));void 0!==d._object.$value&&angular.equals(c,d._object.$value)||angular.equals(c,d._parseObject(d._object))||void 0!==c&&d._loaded&&(d._fRef.set?d._fRef.set(c):d._fRef.ref().update(c))},!0);return a.$on("$destroy",function(){g()}),d._fRef.once("value",function(f){d._timeout(function(){"object"!=typeof f.val()?(null==f.val()&&"function"==typeof c&&(a[b]=c()),e.resolve(g)):d._timeout(function(){null==f.val()&&"function"==typeof c&&(a[b]=c()),e.resolve(g)})})}),e.promise},_parseObject:function(a){function b(a){for(var c in a)a.hasOwnProperty(c)&&("$priority"==c?(a[".priority"]=a.$priority,delete a.$priority):"object"==typeof a[c]&&b(a[c]));return a}var c=b(angular.copy(a));return angular.fromJson(angular.toJson(c))}},angular.module("firebase").factory("$firebaseSimpleLogin",["$q","$timeout","$rootScope",function(a,c,d){return function(e){var f=new b(a,c,d,e);return f.construct()}}]),b=function(a,b,c,d){if(this._q=a,this._timeout=b,this._rootScope=c,this._loginDeferred=null,this._getCurrentUserDeferred=[],this._currentUserData=void 0,"string"==typeof d)throw new Error("Please provide a Firebase reference instead of a URL, eg: new Firebase(url)");this._fRef=d},b.prototype={construct:function(){var a={user:null,$login:this.login.bind(this),$logout:this.logout.bind(this),$createUser:this.createUser.bind(this),$changePassword:this.changePassword.bind(this),$removeUser:this.removeUser.bind(this),$getCurrentUser:this.getCurrentUser.bind(this),$sendPasswordResetEmail:this.sendPasswordResetEmail.bind(this)};if(this._object=a,!window.FirebaseSimpleLogin){var b=new Error("FirebaseSimpleLogin is undefined. Did you forget to include firebase-simple-login.js?");throw this._rootScope.$broadcast("$firebaseSimpleLogin:error",b),b}var c=new FirebaseSimpleLogin(this._fRef,this._onLoginEvent.bind(this));return this._authClient=c,this._object},login:function(a,b){var c=this._q.defer(),d=this;return this.getCurrentUser().then(function(){d._loginDeferred=c,d._authClient.login(a,b)}),c.promise},logout:function(){this._authClient.logout(),delete this._currentUserData},createUser:function(a,b){var c=this,d=this._q.defer();return c._authClient.createUser(a,b,function(a,b){a?(c._rootScope.$broadcast("$firebaseSimpleLogin:error",a),d.reject(a)):d.resolve(b)}),d.promise},changePassword:function(a,b,c){var d=this,e=this._q.defer();return d._authClient.changePassword(a,b,c,function(a){a?(d._rootScope.$broadcast("$firebaseSimpleLogin:error",a),e.reject(a)):e.resolve()}),e.promise},getCurrentUser:function(){var a=this,b=this._q.defer();return void 0!==a._currentUserData?b.resolve(a._currentUserData):a._getCurrentUserDeferred.push(b),b.promise},removeUser:function(a,b){var c=this,d=this._q.defer();return c._authClient.removeUser(a,b,function(a){a?(c._rootScope.$broadcast("$firebaseSimpleLogin:error",a),d.reject(a)):d.resolve()}),d.promise},sendPasswordResetEmail:function(a){var b=this,c=this._q.defer();return b._authClient.sendPasswordResetEmail(a,function(a){a?(b._rootScope.$broadcast("$firebaseSimpleLogin:error",a),c.reject(a)):c.resolve()}),c.promise},_onLoginEvent:function(a,b){if(this._currentUserData!==b||null!==a){var c=this;a?(c._loginDeferred&&(c._loginDeferred.reject(a),c._loginDeferred=null),c._rootScope.$broadcast("$firebaseSimpleLogin:error",a)):(this._currentUserData=b,c._timeout(function(){for(c._object.user=b,b?c._rootScope.$broadcast("$firebaseSimpleLogin:login",b):c._rootScope.$broadcast("$firebaseSimpleLogin:logout"),c._loginDeferred&&(c._loginDeferred.resolve(b),c._loginDeferred=null);c._getCurrentUserDeferred.length>0;){var a=c._getCurrentUserDeferred.pop();a.resolve(b)}}))}}}}();
\ No newline at end of file
diff --git a/bower.json b/bower.json
index b0733549..b39a270d 100644
--- a/bower.json
+++ b/bower.json
@@ -1,15 +1,31 @@
{
"name": "angularfire",
- "version": "0.7.0",
- "main": ["./angularfire.js"],
- "ignore": ["Gruntfile.js", "package.js", "tests", "README.md", ".travis.yml"],
- "dependencies": {
- "angular": "~1.2.0",
- "firebase": "~1.0.5",
- "firebase-simple-login": "~1.3.0"
+ "description": "The officially supported AngularJS binding for Firebase",
+ "version": "2.3.0",
+ "authors": [
+ "Firebase (https://firebase.google.com/)"
+ ],
+ "homepage": "https://github.com/firebase/angularfire",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/firebase/angularfire.git"
},
- "devDependencies": {
- "angular-mocks" : "~1.2.0",
- "observe-js": "~0.1.4"
+ "license": "MIT",
+ "keywords": [
+ "angular",
+ "angularjs",
+ "firebase",
+ "realtime"
+ ],
+ "main": "dist/angularfire.js",
+ "ignore": [
+ "**/*",
+ "!dist/*.js",
+ "!README.md",
+ "!LICENSE"
+ ],
+ "dependencies": {
+ "angular": "^1.3.0",
+ "firebase": "3.x.x"
}
}
diff --git a/changelog.txt b/changelog.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/guide/README.md b/docs/guide/README.md
new file mode 100644
index 00000000..9156e2cd
--- /dev/null
+++ b/docs/guide/README.md
@@ -0,0 +1,9 @@
+# AngularFire Guide
+
+1. [Introduction to AngularFire](introduction-to-angularfire.md) - Learn about what AngularFire is and how to integrate it into your Angular app.
+2. [Synchronized Objects](synchronized-objects.md) - Create synchronized objects and experience three-way data binding.
+3. [Synchronized Arrays](synchronized-arrays.md) - Create and modify arrays which stay in sync with the database.
+4. [Uploading & Downloading Binary Content](uploading-downloading-binary-content.md) - Store and retrieve content like images, audio, and video.
+5. [User Authentication](user-auth.md) - AngularFire handles user authentication and session management for you.
+6. [Extending the Services](extending-services.md) - Advanced users can extend the functionality of the built-in AngularFire services.
+7. [Beyond AngularFire](beyond-angularfire.md) - AngularFire is not the only way to use Angular and Firebase together.
diff --git a/docs/guide/beyond-angularfire.md b/docs/guide/beyond-angularfire.md
new file mode 100644
index 00000000..8e3984a5
--- /dev/null
+++ b/docs/guide/beyond-angularfire.md
@@ -0,0 +1,82 @@
+# Beyond AngularFire | AngularFire Guide
+
+## Table of Contents
+
+* [Overview](#overview)
+* [Best Practices](#best-practices)
+* [Deploying Your App](#deploying-your-app)
+* [Next Steps](#next-steps)
+
+
+## Overview
+
+AngularFire greatly simplifies bindings and abstracts a lot of the internal workings of Angular,
+such as how to notify the compiler when changes occur. However, it does not attempt to replicate
+the entire Firebase client library's API.
+
+There are plenty of use cases for dropping down to the SDK level and using it directly. This
+section will cover a few best practices and techniques for grabbing data directly from our
+database using the JavaScript client library.
+
+This is easiest to accomplish with an example, so read the comments carefully.
+
+```js
+app.controller("SampleCtrl", ["$scope", "$timeout", function($scope, $timeout) {
+ // create a reference to our Firebase database
+ var ref = firebase.database().ref();
+
+ // read data from the database into a local scope variable
+ ref.on("value", function(snapshot) {
+ // Since this event will occur outside Angular's $apply scope, we need to notify Angular
+ // each time there is an update. This can be done using $scope.$apply or $timeout. We
+ // prefer to use $timeout as it a) does not throw errors and b) ensures all levels of the
+ // scope hierarchy are refreshed (necessary for some directives to see the changes)
+ $timeout(function() {
+ $scope.data = snapshot.val();
+ });
+ });
+}]);
+```
+
+Synchronizing simple data like this is trivial. When we start operating on synchronized arrays
+and dealing with bindings, things get a little more interesting. For a comparison of the
+bare-bones work needed to synchronize an array, examine
+[a naive comparison of AngularFire versus the vanilla Firebase client library](https://gist.github.com/katowulf/a8466f4d66a4cea7af7c), and look at
+[Firebase.getAsArray()](https://github.com/katowulf/Firebase.getAsArray) for a more
+fully functional synchronized array implementation and the work involved.
+
+
+## Best Practices
+
+When using the vanilla Firebase client library with Angular, it is best to keep the following things
+in mind:
+
+* **Wrap events in `$timeout()`**: Wrap all server notifications in
+`$timeout()` to ensure the Angular compiler is notified of changes.
+* **Use `$window.Firebase`**: This allows test units and end-to-end
+tests to spy on the Firebase client library and replace it with mock functions. It also avoids the linter warnings about
+globals.
+
+
+## Deploying Your App
+
+Once you are done building your application, you'll need a way to share it with the world. To
+deploy your Angular applications free, fast, and without fuss, do it Firebase style! Our
+production-grade hosting service serves your content over HTTPS and is backed by a global CDN.
+You can deploy your application for free at your very own subdomain of `firebaseapp.com`
+or you can host it at any custom domain on one of our paid plans. Check out
+[Firebase Hosting](https://firebase.google.com/docs/hosting/) for more information.
+
+
+## Next Steps
+
+There are many additional resources for learning about using Firebase with Angular applications:
+
+* Browse the [AngularFire API reference](/docs/reference.md).
+* The [`angularfire-seed`](https://github.com/firebase/angularfire-seed) repo contains a template
+project to help you get started.
+* Check out the [various examples that use AngularFire](/README.md#examples).
+* Join our [Firebase mailing list](https://groups.google.com/forum/#!forum/firebase-talk) to
+keep up to date with any announcements and learn from the AngularFire community.
+* The [`angularfire` tag on Stack Overflow](http://stackoverflow.com/questions/tagged/angularfire)
+has answers to a lot of code-related questions.
diff --git a/docs/guide/extending-services.md b/docs/guide/extending-services.md
new file mode 100644
index 00000000..29d4a9d1
--- /dev/null
+++ b/docs/guide/extending-services.md
@@ -0,0 +1,161 @@
+# Extending Services | AngularFire Guide
+
+## Table of Contents
+
+* [Overview](#overview)
+* [Naming Conventions](#naming-conventions)
+* [Extending `$firebaseObject`](#extending-firebaseobject)
+* [Extending `$firebaseArray`](#extending-firebasearray)
+
+
+## Overview
+
+**This section is intended for experienced Angular users. [Skip ahead](beyond-angularfire.md) if you are just getting started.**
+
+Both the `$firebaseArray` and `$firebaseObject` services provide an
+`$extend()` method for creating new services that inherit from these base classes.
+This allows us to transform data and add additional methods onto our synchronized objects and
+arrays. Before we jump into how exactly to do this, let's discuss some naming conventions used
+within the AngularFire library.
+
+
+## Naming Conventions
+
+Methods in `$firebaseArray` and `$firebaseObject` are named using
+`$`, `$$` or `_` prefixes, according to the following
+convention:
+
+* `$ prefix`: These are **public** methods that exist as part of the
+AngularFire API. They can be overridden using `$extend()`. They should not be removed and must obey the contract specified in the API, as they are used internally by other methods.
+* `$$ prefix`: The methods beginning with `$$` should be considered
+**protected**. They are called by the synchronization code and should not be
+called by other methods, but they may be useful to developers for manipulating data during
+add / update / remove events. They can be overridden with `$extend()`
+but must obey the contract specified in the API.
+* `_ prefix`: Methods and properties beginning with `_` should be considered
+**private**. They are internal methods to the AngularFire code and should not
+be altered or depended on in any way. They can change or disappear in any future release,
+without notice. They are ignored when converting local records to JSON before saving them to the
+Firebase database.
+* `$id`: This special variable is used to track the remote Firebase key. It's used by the
+`$getRecord()` method to find items inside of `$firebaseArray` and is expected to be
+set when `$$added` is invoked.
+* `$value`: This special variable stores primitive values for remote records. For example, if the
+remote value at a path is `"foo"`, and that path is synchronized into a local `$firebaseObject`,
+the locally synchronized object will have a JSON structure `{ "$value": "foo" }`. Similarly, if a
+remote path does not exist, the local object would have the JSON structure `{ "$value": null }`.
+See [Working with Primitives](./synchronized-objects.md#working-with-primitives) for more details.
+
+By default, data stored on a synchronized object or a record in a synchronized array exists
+as a direct attribute of the object. We denote any methods or data which should *not* be
+synchronized with the server by prefixing it with one of these characters. They are automatically
+removed from JSON data before synchronizing this data back to the database.
+Developers may use those prefixes to add additional data / methods to an object or a record
+which they do not want synchronized.
+
+
+## Extending `$firebaseObject`
+
+The following `User` factory retrieves a synchronized user object, and
+adds a special `getFullName()` method.
+
+```js
+app.factory("User", ["$firebaseObject",
+ function($firebaseObject) {
+ // create a new service based on $firebaseObject
+ var User = $firebaseObject.$extend({
+ // these methods exist on the prototype, so we can access the data using `this`
+ getFullName: function() {
+ return this.firstName + " " + this.lastName;
+ }
+ });
+
+ return function(userId) {
+ var userRef = firebase.database().ref()
+ .child("users").child(userId);
+
+ // create an instance of User (the new operator is required)
+ return new User(userRef);
+ }
+ }
+]);
+```
+
+The `new` operator is required for child classes created with the `$extend()` method.
+
+The following special `$$` methods are used by the `$firebaseObject` service
+to notify itself of any server changes. They can be overridden to transform how data is stored
+locally, and what is returned to the server. Read more about them in the
+[API documentation](/docs/reference.md#extending-the-services).
+
+| Method | Description |
+|--------|-------------|
+| `$$updated(snapshot)` | Called with a snapshot any time the value in the database changes. It returns a boolean indicating whether any changes were applied. |
+| `$$error(Object)` | Called if there is a permissions error accessing remote data. Generally these errors are unrecoverable (the data will no longer by synchronized). |
+| `$$defaults(Object)` | 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 the `$$updated` method is invoked. |
+| `toJSON()` | If this method exists, it is used by `JSON.stringify()` to parse the data sent back to the server. |
+
+If you view a `$firebaseObject` in the JavaScript debugger, you may notice a special `$$conf`
+variable. This internal property is used to track internal bindings and state. It is non-enumerable (i.e. it won't
+be iterated by `for` or by `angular.forEach()`) and is also read-only. It is never
+saved back to the server (all `$$` properties are ignored), and it should not be modified or used
+by extending services.
+
+
+## Extending `$firebaseArray`
+
+The following `ListWithTotal` service extends `$firebaseArray` to include a `getTotal()` method.
+
+```js
+app.factory("ListWithTotal", ["$firebaseArray",
+ function($firebaseArray) {
+ // create a new service based on $firebaseArray
+ var ListWithTotal = $firebaseArray.$extend({
+ getTotal: function() {
+ var total = 0;
+ // the array data is located in this.$list
+ angular.forEach(this.$list, function(rec) {
+ total += rec.amount;
+ });
+ return total;
+ }
+ });
+
+ return function(listRef) {
+ // create an instance of ListWithTotal (the new operator is required)
+ return new ListWithTotal(listRef);
+ }
+ }
+]);
+```
+
+The `new` operator is required for child classes created with the `$extend()` method.
+
+The following special `$$` methods are called internally whenever AngularFire receives a notification
+of a server-side change. They can be overridden to transform how data is stored
+locally, and what is returned to the server. Read more about them in the
+[API documentation](/docs/reference.md#extending-the-services).
+
+| Method | Description |
+|--------|-------------|
+| `$$added(snapshot, prevChildKey)` | Called any time a `child_added` event is received. Returns the new record that should be added to the array. The `$getRecord()` method depends on $$added to set the special `$id` variable on each record to the Firebase key. This is used for finding records in the list during `$$added`, `$$updated`, and `$$deleted` events. It is possible to use fields other than `$id` by also overriding how `$getRecord()` matches keys to record in the array. |
+| `$$updated(snapshot)` | Called any time a `child_updated` event is received. Applies the changes and returns `true` if any local data was modified. Uses the `$getRecord()` method to find the correct record in the array for applying updates. Should return `false` if no changes occurred or if the record does not exist in the array. |
+| `$$moved(snapshot, prevChildKey)` | Called any time a `child_moved` event is received. Returns `true` if the record should be moved. The actual move event takes place inside the `$$process` method. |
+| `$$removed(snapshot)` | Called with a snapshot any time a `child_removed` event is received. Depends on the `$getRecord()` method to find the correct record in the array. Returns `true` if the record should be removed. The actual splicing of the array takes place in the `$$process` method. The only responsibility of `$$removed` is deciding if the remove request is valid and if the record exists. |
+| `$$error(errorObject)` | Called if there is a permissions error accessing remote data. Generally these errors are unrecoverable (the data will no longer by synchronized). |
+
+The methods below are also part of extensible portion of `$firebaseArray`, and are used by the event
+methods above, and when saving data back to the Firebase database.
+
+| Method | Description |
+|--------|-------------|
+| `$$defaults(Object)` | 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 the `$add()`, `$$added()`, or `$$updated()`, methods are invoked. |
+| `toJSON()` | If this method exists on a record **in the array**, it is used to parse the data sent back to the server. Thus, by overriding `$$added` to create a toJSON() method on individual records, one can manipulate what data is sent back to Firebase and how it is processed before saving. |
+| `$$process(event, record, prevChildKey)` | This is a mostly internal method and should generally not be overridden. It abstracts some common functionality between the various event types. It's responsible for all inserts, deletes, and splicing of the array element elements, and for calling `$$notify` to trigger notification events. It is called immediately after any server event (`$$added`, `$$updated`, `$$moved` or `$$removed`), assuming those methods do not cancel the event by returning `false` or `null`. |
+| `$$notify(event, recordKey)` | This is a mostly internal method and should generally not be overridden. It triggers notification events for listeners established by `$watch` and is called internally by `$$process`. |
+
+You can read more about extending the `$firebaseObject` and `$firebaseArray`
+services in the
+[API reference](/docs/reference.md#extending-the-services).
+
+The sections of this guide so far have taken us on a tour through the functionality provided by the AngularFire library, but there is still more that can be done with the combination of Firebase and Angular. The [next section](beyond-angularfire.md) takes us beyond AngularFire to see what else is possible.
diff --git a/docs/guide/introduction-to-angularfire.md b/docs/guide/introduction-to-angularfire.md
new file mode 100644
index 00000000..8c59d537
--- /dev/null
+++ b/docs/guide/introduction-to-angularfire.md
@@ -0,0 +1,233 @@
+# Introduction to Firebase | AngularFire Guide
+
+## Table of Contents
+
+* [Overview](#overview)
+* [The Role of AngularFire](#the-role-of-angularfire)
+* [Installing AngularFire](#installing-angularfire)
+* [Handling Asynchronous Operations](#handling-asynchronous-operations)
+
+
+## Overview
+
+Firebase provides several key advantages for [Angular](https://angular.io/) applications:
+
+1. **Lightning-fast data synchronization:** Firebase can serve as your entire backend service, not
+ only persisting your data, but synchronizing it instantly between millions of connected clients.
+2. **No backend server:** Utilizing only the Firebase JavaScript SDK and AngularFire, combined with
+ our [flexible Security Rules](https://firebase.google.com/docs/database/security/) rules, you can
+ have complete control of your data without any server-side hardware or code.
+3. **Built-in authentication:** Firebase provides an [authentication and user management
+ service](https://firebase.google.com/docs/auth/) which interfaces with OAuth service
+ providers like Facebook and Twitter, as well as anonymous and email / password authentication
+ tools. You can even integrate with an existing authentication service using Firebase custom
+ authentication.
+4. **Free hosting:** Every Firebase app comes with [free hosting](https://firebase.google.com/docs/hosting/)
+ served over a secure SSL connection and backed by a global CDN. You can deploy your static HTML,
+ JavaScript, and CSS files to the web in seconds.
+5. **Magical data bindings:** Our AngularFire library works like *glue* between Angular's two-way
+ bindings and Firebase's scalable synchronization platform.
+
+
+## The Role of AngularFire
+
+AngularFire is an [open source library](https://github.com/firebase/angularfire) maintained by the
+Firebase team and our amazing community of developers. It provides three-way communication between
+your Firebase database and Angular's DOM - JavaScript bindings.
+
+If you are unfamiliar with Firebase, we suggest you start by reading through the [Firebase web
+guide](https://firebase.google.com/docs/database/web/start). It is important to understand the
+fundamental principles of how to structure data in your Firebase database and how to read and write
+from it before diving into AngularFire. These bindings are meant to complement the core Firebase
+client library, not replace it entirely by adding `$` signs in front of the methods.
+
+AngularFire is also not ideal for synchronizing deeply nested collections inside of collections. In
+general, deeply nested collections [should typically be avoided](https://firebase.google.com/docs/database/web/structure-data)
+in distributed systems.
+
+While AngularFire abstracts a lot of complexities involved in synchronizing data, it is not required
+to use Angular with Firebase. Alternatives are covered in the [Beyond AngularFire](./beyond-angularfire.md)
+section of this guide.
+
+
+## Installing AngularFire
+
+Adding Firebase to your application is easy. Simply include the Firebase JavaScript SDK and the
+AngularFire bindings from our CDN:
+
+```html
+
+
+
+
+
+
+
+
+```
+
+Firebase and AngularFire are also available via npm and Bower as `firebase` and `angularfire`,
+respectively. A [Yeoman generator](https://github.com/firebase/generator-angularfire) is also
+available.
+
+Once we have our libraries installed, we can include the AngularFire services by declaring
+`firebase` as a module dependency in our application.
+
+```js
+var app = angular.module("sampleApp", ["firebase"]);
+```
+
+We now will have access to three services provided by AngularFire: `$firebaseObject`,
+`$firebaseArray`, and `$firebaseAuth`. To use these services, we need to inject them into a
+controller, factory, or service.
+
+```js
+app.controller("SampleController", ["$scope", "$firebaseArray",
+ function($scope, $firebaseArray) {
+ // ...
+ }
+]);
+```
+
+Let's see it in action! The live code example below is a working demo of a rudimentary chat room.
+It binds an Angular view to a Firebase backend, synchronizing a list of messages between the DOM,
+Angular, and Firebase in realtime. It doesn't seem like much code for all of this, and that's part
+of the magic!
+
+```js
+// define our app and dependencies (remember to include firebase!)
+var app = angular.module("sampleApp", ["firebase"]);
+
+// this factory returns a synchronized array of chat messages
+app.factory("chatMessages", ["$firebaseArray",
+ function($firebaseArray) {
+ // create a reference to the database location where we will store our data
+ var ref = firebase.database().ref();
+
+ // this uses AngularFire to create the synchronized array
+ return $firebaseArray(ref);
+ }
+]);
+
+app.controller("ChatCtrl", ["$scope", "chatMessages",
+ // we pass our new chatMessages factory into the controller
+ function($scope, chatMessages) {
+ $scope.user = "Guest " + Math.round(Math.random() * 100);
+
+ // we add chatMessages array to the scope to be used in our ng-repeat
+ $scope.messages = chatMessages;
+
+ // a method to create new messages; called by ng-submit
+ $scope.addMessage = function() {
+ // calling $add on a synchronized array is like Array.push(),
+ // except that it saves the changes to our database!
+ $scope.messages.$add({
+ from: $scope.user,
+ content: $scope.message
+ });
+
+ // reset the message input
+ $scope.message = "";
+ };
+
+ // if the messages are empty, add something for fun!
+ $scope.messages.$loaded(function() {
+ if ($scope.messages.length === 0) {
+ $scope.messages.$add({
+ from: "Firebase Docs",
+ content: "Hello world!"
+ });
+ }
+ });
+ }
+]);
+```
+
+```html
+
+
+
{{ message.from }}: {{ message.content }}
+
+
+
+```
+
+The primary purpose of AngularFire is to manage synchronized data, which is exposed through the
+`$firebaseObject` and `$firebaseArray` services. These services are aware of how Angular's
+[compile process works](https://docs.angularjs.org/guide/compiler), and notifies it at the correct
+points to check `$digest` for changes and update the DOM. If that sounds like a foreign language,
+that's okay! AngularFire is taking care of it, so don't worry.
+
+It's not always necessary to set up AngularFire bindings to interact with the database. This is
+particularly true when just writing data, and not synchronizing it locally. Since you already have
+a database reference handy, it is perfectly acceptable to simply use the vanilla Firebase client
+library API methods.
+
+```js
+var ref = firebase.database().ref();
+// We don't always need AngularFire!
+//var obj = $firebaseObject(ref);
+// For example, if we just want to increment a counter, which we aren't displaying locally,
+// we can just set it using the SDK
+ref.child("foo/counter").transaction(function(currentValue) {
+ return (currentValue || 0) + 1;
+});
+```
+
+
+## Handling Asynchronous Operations
+
+Data is synchronized with our database *asynchronously*. This means that calls to the remote server
+take some time to execute, but the code keeps running in the meantime. Thus, we have to be careful
+to wait for the server to return data before we can access it.
+
+The easiest way to log the data is to print it within the view using Angular's `json` filter.
+AngularFire tells the Angular compiler when it has finished loading the data, so there is no need to
+worry about when it be available.
+
+```html
+
{{ data | json }}
+```
+
+It's also possible to do this directly in the controller by using the
+[`$loaded()`](/docs/reference.md#loaded) method.
+However, this method should be used with care as it's only called once after initial load. Using it
+for anything but debugging is usually a poor practice.
+
+```js
+var ref = firebase.database().ref();
+$scope.data = $firebaseObject(ref);
+// this waits for the data to load and then logs the output. Therefore,
+// data from the server will now appear in the logged output. Use this with care!
+$scope.data.$loaded()
+ .then(function() {
+ console.log($scope.data);
+ })
+ .catch(function(err) {
+ console.error(err);
+ });
+```
+
+When working directly with the SDK, it's important to notify Angular's compiler after the data has
+been loaded:
+
+```js
+var ref = firebase.database().ref();
+ref.on("value", function(snapshot) {
+ // This isn't going to show up in the DOM immediately, because
+ // Angular does not know we have changed this in memory.
+ // $scope.data = snapshot.val();
+ // To fix this, we can use $scope.$apply() to notify Angular that a change occurred.
+ $scope.$apply(function() {
+ $scope.data = snapshot.val();
+ });
+});
+```
+
+Now that we understand the basics of integrating AngularFire into our application, let's dive deeper
+into reading and writing synchronized data with our database. The
+[next section](synchronized-objects.md) introduces the `$firebaseObject` service for creating
+synchronized objects.
diff --git a/docs/guide/synchronized-arrays.md b/docs/guide/synchronized-arrays.md
new file mode 100644
index 00000000..0fc39fe8
--- /dev/null
+++ b/docs/guide/synchronized-arrays.md
@@ -0,0 +1,215 @@
+# Synchronized Arrays | AngularFire Guide
+
+## Table of Contents
+
+* [Overview](#overview)
+* [API Summary](#api-summary)
+* [Meta Fields on the Array](#meta-fields-on-the-array)
+* [Modifying the Synchronized Array](#modifying-the-synchronized-array)
+* [Full Example](#full-example)
+
+
+## Overview
+
+Synchronized arrays should be used for any list of objects that will be sorted, iterated, and which
+have unique IDs. The synchronized array assumes that items are added using
+[`$add()`](/docs/reference.md#addnewdata), and
+that they will therefore be keyed using Firebase
+[push IDs](https://firebase.google.com/docs/database/web/save-data).
+
+We create a synchronized array with the `$firebaseArray` service. The array is [sorted in the same
+order](https://firebase.google.com/docs/database/web/retrieve-data#sort_data) as the records on the server. In
+other words, we can pass a [query](https://firebase.google.com/docs/database/web/retrieve-data#filtering_data)
+into the synchronized array, and the records will be sorted according to query criteria.
+
+While the array isn't technically read-only, it has some special requirements for modifying the
+structure (removing and adding items) which we will cover below. Please read through this entire
+section before trying any slicing or dicing of the array.
+
+```js
+// define our app and dependencies (remember to include firebase!)
+var app = angular.module("sampleApp", ["firebase"]);
+// inject $firebaseArray into our controller
+app.controller("ProfileCtrl", ["$scope", "$firebaseArray",
+ function($scope, $firebaseArray) {
+ var messagesRef = firebase.database().ref().child("messages");
+ // download the data from a Firebase reference into a (pseudo read-only) array
+ // all server changes are applied in realtime
+ $scope.messages = $firebaseArray(messagesRef);
+ // create a query for the most recent 25 messages on the server
+ var query = messagesRef.orderByChild("timestamp").limitToLast(25);
+ // the $firebaseArray service properly handles database queries as well
+ $scope.filteredMessages = $firebaseArray(query);
+ }
+]);
+```
+
+We can now utilize this array as expected with Angular directives.
+
+```html
+
+
{{ message.user }}: {{ message.text }}
+
+```
+
+To add a button for removing messages, we can make use of `$remove()`, passing it the message we
+want to remove:
+
+```html
+
+
+ {{ message.user }}: {{ message.text }}
+
+
+
+```
+
+We also have access to the key for the node where each message is located via `$id`:
+
+```html
+
+
+ Message data located at node /messages/{{ message.$id }}
+
+
+```
+
+
+## API Summary
+
+The table below highlights some of the common methods on the synchronized array. The complete list
+of methods can be found in the
+[API documentation](/docs/reference.md#firebasearray) for
+`$firebaseArray`.
+
+| Method | Description |
+| ------------- | ------------- |
+| [`$add(data)`](/docs/reference.md#addnewdata) | Creates a new record in the array. Should be used in place of `push()` or `splice()`. |
+| [`$remove(recordOrIndex)`](/docs/reference.md#removerecordorindex) | Removes an existing item from the array. Should be used in place of `pop()` or `splice()`. |
+| [`$save(recordOrIndex)`](/docs/reference.md#saverecordorindex) | Saves an existing item in the array. |
+| [`$getRecord(key)`](/docs/reference.md#getrecordkey) | Given a Firebase database key, returns the corresponding item from the array. It is also possible to find the index with `$indexFor(key)`. |
+| [`$loaded()`](/docs/reference.md#loaded-1) | Returns a promise which resolves after the initial records have been downloaded from our database. This is only called once and should be used with care. See [Extending Services](extending-services.md) for more ways to hook into server events. |
+
+
+## Meta Fields on the Array
+
+Similar to synchronized objects, each item in a synchronized array will contain the following special attributes:
+
+| Method | Description |
+| ------------- | ------------- |
+| `$id` | The key for each record. This is equivalent to each record's path in our database as it would be returned by `ref.key()`. |
+| `$priority` | The [priority](https://firebase.google.com/docs/database/web/retrieve-data#sorting_and_filtering_data) of each child node is stored here for reference. Changing this value and then calling `$save()` on the record will also change the priority on the server and potentially move the record in the array. |
+| `$value` | If the data for this child node is a primitive (number, string, or boolean), then the record itself will still be an object. The primitive value will be stored under `$value` and can be changed and saved like any other field. |
+
+
+## Modifying the Synchronized Array
+
+The contents of this array are synchronized with a remote server, and AngularFire handles adding,
+removing, and ordering the elements. Because of this special arrangement, AngularFire provides the
+concurrency safe `$add()`, `$remove()`, and `$save()` methods to modify the array and its elements.
+
+Using methods like `splice()`, `pop()`, `push()`, `shift()`, and `unshift()` will probably work for
+modifying the local content, but those methods are not monitored by AngularFire and changes
+introduced won't affect the content or order on the remote server. Therefore, to change the remote
+data, the concurrency-safe methods should be used instead.
+
+```js
+var messages = $FirebaseArray(ref);
+// add a new record to the list
+messages.$add({
+ user: "physicsmarie",
+ text: "Hello world"
+});
+// remove an item from the list
+messages.$remove(someRecordKey);
+// change a message and save it
+var item = messages.$getRecord(someRecordKey);
+item.user = "alanisawesome";
+messages.$save(item).then(function() {
+ // data has been saved to our database
+});
+```
+
+
+## Full Example
+
+Using those methods together, we can synchronize collections between multiple clients, and
+manipulate the records in the collection:
+
+```js
+var app = angular.module("sampleApp", ["firebase"]);
+
+app.factory("chatMessages", ["$firebaseArray",
+ function($firebaseArray) {
+ // create a reference to the database where we will store our data
+ var ref = firebase.database().ref();
+
+ return $firebaseArray(ref);
+ }
+]);
+
+app.controller("ChatCtrl", ["$scope", "chatMessages",
+ function($scope, chatMessages) {
+ $scope.user = "Guest " + Math.round(Math.random() * 100);
+
+ $scope.messages = chatMessages;
+
+ $scope.addMessage = function() {
+ // $add on a synchronized array is like Array.push() except it saves to the database!
+ $scope.messages.$add({
+ from: $scope.user,
+ content: $scope.message,
+ timestamp: firebase.database.ServerValue.TIMESTAMP
+ });
+
+ $scope.message = "";
+ };
+
+ // if the messages are empty, add something for fun!
+ $scope.messages.$loaded(function() {
+ if ($scope.messages.length === 0) {
+ $scope.messages.$add({
+ from: "Uri",
+ content: "Hello!",
+ timestamp: firebase.database.ServerValue.TIMESTAMP
+ });
+ }
+ });
+ }
+]);
+```
+
+```html
+
+```
+
+Head on over to the [API reference](/docs/reference.md#firebasearray)
+for `$firebaseArray` to see more details for each API method provided by the service. Now that we
+have a grasp of synchronizing data with AngularFire, the [next section](uploading-downloading-binary-content.md) of this guide
+moves on to a different aspect of building applications: binary storage.
diff --git a/docs/guide/synchronized-objects.md b/docs/guide/synchronized-objects.md
new file mode 100644
index 00000000..de946887
--- /dev/null
+++ b/docs/guide/synchronized-objects.md
@@ -0,0 +1,245 @@
+# Synchronized Objects | AngularFire Guide
+
+## Table of Contents
+
+* [Overview](#overview)
+* [API Summary](#api-summary)
+* [Meta Fields on the Object](#meta-fields-on-the-object)
+* [Full Example](#full-example)
+* [Three-way Data Bindings](#three-way-data-bindings)
+* [Working With Primitives](#working-with-primitives)
+
+
+## Overview
+
+Objects are useful for storing key / value pairs and singular records that are not used as a
+collection. Consider the following user profile for `physicsmarie`:
+
+```js
+{
+ "profiles": {
+ "physicsmarie": {
+ name: "Marie Curie",
+ dob: "November 7, 1867"
+ }
+ }
+}
+```
+
+We could fetch this profile using AngularFire's `$firebaseObject()` service. In addition to several
+helper methods prefixed with `$`, the returned object would contain all of the child keys for that
+record (i.e. `name` and `dob`).
+
+```js
+// define our app and dependencies (remember to include firebase!)
+var app = angular.module("sampleApp", ["firebase"]);
+// inject $firebaseObject into our controller
+app.controller("ProfileCtrl", ["$scope", "$firebaseObject",
+ function($scope, $firebaseObject) {
+ var ref = firebase.database().ref();
+ // download physicsmarie's profile data into a local object
+ // all server changes are applied in realtime
+ $scope.profile = $firebaseObject(ref.child('profiles').child('physicsmarie'));
+ }
+]);
+```
+
+The data will be requested from the server and, when it returns, AngularFire will notify the Angular
+compiler to render the new content. So we can use this in our views normally. For example, the code
+below would print the content of the profile in JSON format.
+
+```html
+
{{ profile | json }}
+```
+
+Changes can be saved back to the server using the
+[`$save()`](/docs/reference.md#save) method.
+This could, for example, be attached to an event in the DOM view, such as `ng-click` or `ng-change`.
+
+```html
+
+```
+
+
+## API Summary
+
+The synchronized object is created with several special $ properties, all of which are listed in the following table:
+
+| Method | Description |
+| ------------- | ------------- |
+| [`$save()`](/docs/reference.md#save) | Synchronizes local changes back to the remote database. |
+| [`$remove()`](/docs/reference.md#remove) | Removes the object from the database, deletes the local object's keys, and sets the local object's `$value` to `null`. It's important to note that the object still exists locally, it is simply empty and we are now treating it as a primitive with a value of `null`. |
+| [`$loaded()`](/docs/reference.md#loaded) | Returns a promise which is resolved when the initial server data has been downloaded. |
+| [`$bindTo()`](/docs/reference.md#bindtoscope-varname) | Creates a three-way data binding. Covered below in the [Three-way Data Bindings](#three-way-data-bindings) section. |
+
+
+## Meta Fields on the Object
+
+The synchronized object is created with several special `$` properties, all of which are listed in the following table:
+
+| Method | Description |
+| ------------- | ------------- |
+| [`$id`](/docs/reference.md#id) | The key for this record. This is equivalent to this object's path in our database as it would be returned by `ref.key()`. |
+| [`$priority`](/docs/reference.md#priority) | The priority of each child node is stored here for reference. Changing this value and then calling `$save()` on the record will also change the object's priority on the server. |
+| [`$value`](/docs/reference.md#value) | If the data in our database is a primitive (number, string, or boolean), the `$firebaseObject()` service will still return an object. The primitive value will be stored under `$value` and can be changed and saved like any other child node. See [Working with Primitives](#working-with-primitives) for more details. |
+
+
+## Full Example
+
+Putting all of that together, we can generate a page for editing user profiles:
+
+```js
+var app = angular.module("sampleApp", ["firebase"]);
+
+// a factory to create a re-usable profile object
+// we pass in a username and get back their synchronized data
+app.factory("Profile", ["$firebaseObject",
+ function($firebaseObject) {
+ return function(username) {
+ // create a reference to the database node where we will store our data
+ var ref = firebase.database().ref("rooms").push();
+ var profileRef = ref.child(username);
+
+ // return it as a synchronized object
+ return $firebaseObject(profileRef);
+ }
+ }
+]);
+
+app.controller("ProfileCtrl", ["$scope", "Profile",
+ function($scope, Profile) {
+ // put our profile in the scope for use in DOM
+ $scope.profile = Profile("physicsmarie");
+
+ // calling $save() on the synchronized object syncs all data back to our database
+ $scope.saveProfile = function() {
+ $scope.profile.$save().then(function() {
+ alert('Profile saved!');
+ }).catch(function(error) {
+ alert('Error!');
+ });
+ };
+ }
+]);
+```
+
+```html
+
+
+
Edit {{ profile.$id }}
+
+
+
+
+```
+
+
+## Three-way Data Bindings
+
+Synchronizing changes from the server is pretty magical. However, shouldn't an awesome tool like
+AngularFire have some way to detect local changes as well so we don't have to call `$save()`? Of
+course. We call this a *three-way data binding*.
+
+Simply call `$bindTo()` on a synchronized object and now any changes in the DOM are pushed to
+Angular, and then automatically to our database. And inversely, any changes on the server get pushed
+into Angular and straight to the DOM.
+
+Let's revise our previous example to get rid of the pesky save button and the `$save()` method:
+
+```js
+var app = angular.module("sampleApp", ["firebase"]);
+
+// a factory to create a re-usable Profile object
+// we pass in a username and get back their synchronized data as an object
+app.factory("Profile", ["$firebaseObject",
+ function($firebaseObject) {
+ return function(username) {
+ // create a reference to the database node where we will store our data
+ var ref = firebase.database().ref("rooms").push();
+ var profileRef = ref.child(username);
+
+ // return it as a synchronized object
+ return $firebaseObject(profileRef);
+ }
+ }
+]);
+
+app.controller("ProfileCtrl", ["$scope", "Profile",
+ function($scope, Profile) {
+ // create a three-way binding to our Profile as $scope.profile
+ Profile("physicsmarie").$bindTo($scope, "profile");
+ }
+]);
+```
+
+```html
+
+
+
Edit {{ profile.$id }}
+
+
+
+
+
+
+
+
+
+```
+
+In this example, we've used `$bindTo()` to automatically synchronize data between the database and
+`$scope.profile`. We don't need an `ng-submit` to call `$save()` anymore. AngularFire takes care of
+all this automatically!
+
+**While three-way data bindings can be extremely convenient, be careful of trying to use them
+against deeply nested tree structures. For performance reasons, stick to practical uses like
+synchronizing key / value pairs that aren't changed simultaneously by several users. Do not try to
+use `$bindTo()` to synchronize collections or lists of data.**
+
+
+## Working With Primitives
+
+Consider the following data structure in Firebase:
+
+```js
+{
+ "foo": "bar"
+}
+```
+
+If we attempt to synchronize `foo/` into a `$firebaseObject`, the special `$value` key is created to
+store the primitive. This key only exists when the path contains no child nodes. For a path that
+doesn't exist, `$value` would be set to `null`.
+
+```js
+var ref = firebase.database().ref().child("push");
+var obj = new $firebaseObject(ref);
+obj.$loaded().then(function() {
+ console.log(obj.$value); // "bar"
+});
+
+// change the value at path foo/ to "baz"
+obj.$value = "baz";
+obj.$save();
+
+// delete the value and see what is returned
+obj.$remove().then(function() {
+ console.log(obj.$value); // null!
+});
+```
+
+Head on over to the [API reference](/docs/reference.md#firebaseobject)
+for `$firebaseObject` to see more details for each API method provided by the service. But not all
+of your data is going to fit nicely into a plain JavaScript object. Many times you will have lists
+of data instead. In those cases, you should use AngularFire's `$firebaseArray` service, which we
+will discuss in the [next section](synchronized-arrays.md).
diff --git a/docs/guide/uploading-downloading-binary-content.md b/docs/guide/uploading-downloading-binary-content.md
new file mode 100644
index 00000000..93fe0cb5
--- /dev/null
+++ b/docs/guide/uploading-downloading-binary-content.md
@@ -0,0 +1,152 @@
+# Uploading & Downloading Binary Content | AngularFire Guide
+
+## Table of Contents
+
+* [Overview](#overview)
+* [API Summary](#api-summary)
+* [Uploading Files](#uploading-files)
+* [Displaying Images with the `firebase-src` Directive](#displaying-images-with-the-firebase-src-directive)
+* [Retrieving Files from the Template](#retrieving-files-from-the-template)
+
+## Overview
+
+Firebase provides [a hosted binary storage service](https://firebase.google.com/docs/storage/)
+which enables you to store and retrieve user-generated content like images, audio, and
+video directly from the Firebase client SDK.
+
+Binary files are stored in a Cloud Storage bucket, not in the Realtime Database.
+The files in your bucket are stored in a hierarchical structure, just like
+in the Realtime Database.
+
+To use the Cloud Storage for Firebase binding, first [create a Storage reference](https://firebase.google.com/docs/storage/web/create-reference).
+Then, using this reference, pass it into the `$firebaseStorage` service:
+
+```js
+// define our app and dependencies (remember to include firebase!)
+angular
+ .module("sampleApp", [
+ "firebase"
+ ])
+ .controller("SampleCtrl", SampleCtrl);
+
+// inject $firebaseStorage into our controller
+function SampleCtrl($firebaseStorage) {
+ // create a Storage reference for the $firebaseStorage binding
+ var storageRef = firebase.storage().ref("userProfiles/physicsmarie");
+ var storage = $firebaseStorage(storageRef);
+}
+SampleCtrl.$inject = ["$firebaseStorage"];
+```
+
+## API Summary
+
+The Cloud Storage for Firebase service is created with several special `$` methods, all of which are listed in the following table:
+
+| Method | Description |
+| ------------- | ------------- |
+| [`$put(file, metadata)`](/docs/reference.md#putfile-metadata) | Uploads file to configured path with optional metadata. Returns an AngularFire wrapped [`UploadTask`](/docs/reference.md#upload-task). |
+| [`$putString(string, format, metadata)`](/docs/reference.md#putstringstring-format-metadata) | Uploads a upload a raw, base64, or base64url encoded string with optional metadata. Returns an AngularFire wrapped [`UploadTask`](/docs/reference.md#upload-task). |
+| [`$getDownloadURL()`](/docs/reference.md#getdownloadurl) | Returns a `Promise` fulfilled with the download URL for the file stored at the configured path. |
+| [`$getMetadata()`](/docs/reference.md#getmetadata) | Returns a `Promise` fulfilled with the metadata of the file stored at the configured path. |
+| [`$updateMetadata(metadata)`](/docs/reference.md#updatemetadatametadata) | Returns a `Promise` containing the updated metadata. |
+| [`$delete()`](/docs/reference.md#delete) | Permanently deletes the file stored at the configured path. Returns a `Promise` that is resolved when the delete completes. |
+| [`$toString()`](/docs/reference.md#tostring) | Returns a string version of the bucket path stored as a `gs://` scheme. |
+
+
+## Uploading files
+To upload files, use either the `$put()` or `$putString()` methods. These methods
+return an [[`UploadTask`](/docs/reference.md#upload-task)(https://firebase.google.com/docs/reference/js/firebase.storage#uploadtask) which is wrapped by AngularFire to handle the `$digest` loop.
+
+```js
+function SampleCtrl($firebaseStorage) {
+ // create a Storage reference for the $firebaseStorage binding
+ var storageRef = firebase.storage().ref('userProfiles/physicsmarie');
+ var storage = $firebaseStorage(storageRef);
+ var file = // get a file from the template (see Retrieving files from template section below)
+ var uploadTask = storage.$put(file);
+ // of upload via a RAW, base64, or base64url string
+ var stringUploadTask = storage.$putString('5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB', 'base64');
+}
+SampleCtrl.$inject = ["$firebaseStorage"];
+```
+
+### Upload Task API Summary
+
+| Method | Description |
+| ------------- | ------------- |
+| [`$progress(callback)`](/docs/reference.md#progresscallback) | Calls the provided callback function whenever there is an update in the progress of the file uploading. |
+| [`$error(callback)`](/docs/reference.md#errorcallback) | Calls the provided callback function when there is an error uploading the file. |
+| [`$complete(callback)`](/docs/reference.md#completecallback) | Calls the provided callback function when the upload is complete. |
+| [`$cancel()`](/docs/reference.md#cancel) | Cancels the upload. |
+| [`$pause()`](/docs/reference.md#pause) | Pauses the upload. |
+| [`$snapshot()`](/docs/reference.md#snapshot) | Returns the [current immutable view of the task](https://firebase.google.com/docs/storage/web/upload-files#monitor_upload_progress) at the time the event occurred. |
+| [`then(callback)`](/docs/reference.md#then) | An [`UploadTask`](/docs/reference.md#upload-task) implements a `Promise` like interface. This callback is called when the upload is complete. |
+| [`catch(callback)`](/docs/reference.md#catch) | An [`UploadTask`](/docs/reference.md#upload-task) implements a `Promise` like interface. This callback is called when an error occurs. |
+
+## Displaying Images with the `firebase-src` Directive
+
+AngularFire provides a directive that displays a file with any `src`-compatible element. Instead of using the tradional `src` attribute, use `firebase-src`:
+
+```html
+
+
+
+```
+
+## Retrieving Files from the Template
+
+AngularFire does not provide a directive for retrieving an uploaded file. However,
+the directive below provides a baseline to work off:
+
+```js
+angular
+ .module("sampleApp", [
+ "firebase"
+ ])
+ .directive("fileUpload", FileUploadDirective);
+
+function FileUploadDirective() {
+ return {
+ restrict: "E",
+ transclude: true,
+ scope: {
+ onChange: "="
+ },
+ template: '',
+ link: function (scope, element, attrs) {
+ element.bind("change", function () {
+ scope.onChange(element.children()[0].files);
+ });
+ }
+ }
+}
+```
+
+To use this directive, create a controller to bind the `onChange()` method:
+
+```js
+function UploadCtrl($firebaseStorage) {
+ var ctrl = this;
+ var storageRef = firebase.storage().ref("userProfiles/physicsmarie");
+ var storage = $firebaseStorage(storageRef);
+ ctrl.fileToUpload = null;
+ ctrl.onChange = function onChange(fileList) {
+ ctrl.fileToUpload = fileList[0];
+ };
+}
+```
+
+Then specify your template to use the directive:
+
+```html
+
+
+ Upload
+
+
+```
+
+Head on over to the [API reference](/docs/reference.md#firebasestorage)
+for `$firebaseStorage` to see more details for each API method provided by the service. Now that we
+have a grasp of managing binary content with AngularFire, the [next section](user-auth.md) of this guide
+moves on to a new topic: authentication.
diff --git a/docs/guide/user-auth.md b/docs/guide/user-auth.md
new file mode 100644
index 00000000..d127f8f4
--- /dev/null
+++ b/docs/guide/user-auth.md
@@ -0,0 +1,420 @@
+# User Auth | AngularFire Guide
+
+## Table of Contents
+
+* [Overview](#overview)
+* [Signing Users In](#signing-users-in)
+* [Managing Users](#managing-users)
+* [Retrieving Authentication State](#retrieving-authentication-state)
+* [User-Based Security](#user-based-security)
+* [Authenticating With Routers](#authenticating-with-routers)
+ - [`ngRoute` Example](#ngroute-example)
+ - [`ui-router` Example](#ui-router-example)
+
+
+## Overview
+
+Firebase provides [a hosted authentication service](https://firebase.google.com/docs/auth/) which
+provides a completely client-side solution to user management and authentication. It supports
+anonymous authentication, email / password sign in, and sign in via several OAuth providers, including
+Facebook, GitHub, Google, and Twitter.
+
+Each provider has to be configured individually and also enabled from the **Auth** tab of
+your [Firebase Console](https://console.firebase.google.com). Select a provider from the table below
+to learn more.
+
+| Provider | Description |
+|----------|-------------|
+| [Custom](https://firebase.google.com/docs/auth/web/custom-auth) | Generate your own authentication tokens. Use this to integrate with existing authentication systems. You can also use this to authenticate server-side workers. |
+| [Email & Password](https://firebase.google.com/docs/auth/web/password-auth) | Let Firebase manage passwords for you. Register and authenticate users by email & password. |
+| [Anonymous](https://firebase.google.com/docs/auth/web/anonymous-auth) | Build user-centric functionality without requiring users to share their personal information. Anonymous authentication generates a unique identifier for each user that lasts as long as their session. |
+| [Facebook](https://firebase.google.com/docs/auth/web/facebook-login) | Authenticate users with Facebook by writing only client-side code. |
+| [Twitter](https://firebase.google.com/docs/auth/web/twitter-login) | Authenticate users with Twitter by writing only client-side code. |
+| [GitHub](https://firebase.google.com/docs/auth/web/github-auth) | Authenticate users with GitHub by writing only client-side code. |
+| [Google](https://firebase.google.com/docs/auth/web/google-signin) | Authenticate users with Google by writing only client-side code. |
+
+AngularFire provides a service named `$firebaseAuth` which wraps the authentication methods provided
+by the Firebase client library. It can be injected into any controller, service, or factory.
+
+```js
+// define our app and dependencies (remember to include firebase!)
+var app = angular.module("sampleApp", ["firebase"]);
+
+// inject $firebaseAuth into our controller
+app.controller("SampleCtrl", ["$scope", "$firebaseAuth",
+ function($scope, $firebaseAuth) {
+ var auth = $firebaseAuth();
+ }
+]);
+```
+
+
+## Signing Users In
+
+The `$firebaseAuth` service has methods for each authentication type. For example, to authenticate
+an anonymous user, you can use `$signInAnonymously()`:
+
+```js
+var app = angular.module("sampleApp", ["firebase"]);
+
+app.controller("SampleCtrl", ["$scope", "$firebaseAuth",
+ function($scope, $firebaseAuth) {
+ var auth = $firebaseAuth();
+
+ $scope.signIn = function() {
+ $scope.firebaseUser = null;
+ $scope.error = null;
+
+ auth.$signInAnonymously().then(function(firebaseUser) {
+ $scope.firebaseUser = firebaseUser;
+ }).catch(function(error) {
+ $scope.error = error;
+ });
+ };
+ }
+]);
+```
+
+```html
+
+
+
+
Signed in user: {{ firebaseUser.uid }}
+
Error: {{ error }}
+
+```
+
+
+## Managing Users
+
+The `$firebaseAuth` service also provides [a full suite of methods](/docs/reference.md#firebaseauth)
+for managing users. This includes methods for creating and removing users, changing an users's email
+or password, and sending email verification and password reset emails. The following example gives
+you a taste of just how easy this is:
+
+```js
+var app = angular.module("sampleApp", ["firebase"]);
+
+// let's create a re-usable factory that generates the $firebaseAuth instance
+app.factory("Auth", ["$firebaseAuth",
+ function($firebaseAuth) {
+ return $firebaseAuth();
+ }
+]);
+
+// and use it in our controller
+app.controller("SampleCtrl", ["$scope", "Auth",
+ function($scope, Auth) {
+ $scope.createUser = function() {
+ $scope.message = null;
+ $scope.error = null;
+
+ // Create a new user
+ Auth.$createUserWithEmailAndPassword($scope.email, $scope.password)
+ .then(function(firebaseUser) {
+ $scope.message = "User created with uid: " + firebaseUser.uid;
+ }).catch(function(error) {
+ $scope.error = error;
+ });
+ };
+
+ $scope.deleteUser = function() {
+ $scope.message = null;
+ $scope.error = null;
+
+ // Delete the currently signed-in user
+ Auth.$deleteUser().then(function() {
+ $scope.message = "User deleted";
+ }).catch(function(error) {
+ $scope.error = error;
+ });
+ };
+ }
+]);
+```
+
+```html
+
+ Email:
+ Password:
+
+
+
+
+
+
+
+
+
+
Message: {{ message }}
+
Error: {{ error }}
+
+```
+
+
+## Retrieving Authentication State
+
+Whenever a user is authenticated, you can use the synchronous [`$getAuth()`](/docs/reference.md#getauth)
+method to retrieve the client's current authentication state. This includes the authenticated user's
+`uid` (a user identifier which is unique across all providers), the `providerId` used to
+authenticate (e.g. `google.com`, `facebook.com`), as well as other properties
+[listed here](https://firebase.google.com/docs/reference/js/firebase.User#properties). Additional
+variables are included for each specific provider and are covered in the provider-specific links in
+the table above.
+
+In addition to the synchronous `$getAuth()` method, there is also an asynchronous
+[`$onAuthStateChanged()`](/docs/reference.md#onauthstatechangedcallback-context) method which fires a
+user-provided callback every time authentication state changes. This is often more convenient than
+using `$getAuth()` since it gives you a single, consistent place to handle updates to authentication
+state, including users signing in or out and sessions expiring.
+
+Pulling some of these concepts together, we can create a sign in form with dynamic content based on
+the user's current authentication state:
+
+```js
+var app = angular.module("sampleApp", ["firebase"]);
+
+app.factory("Auth", ["$firebaseAuth",
+ function($firebaseAuth) {
+ return $firebaseAuth();
+ }
+]);
+
+app.controller("SampleCtrl", ["$scope", "Auth",
+ function($scope, Auth) {
+ $scope.auth = Auth;
+
+ // any time auth state changes, add the user data to scope
+ $scope.auth.$onAuthStateChanged(function(firebaseUser) {
+ $scope.firebaseUser = firebaseUser;
+ });
+ }
+]);
+```
+
+```html
+
+
+
Hello, {{ firebaseUser.displayName }}
+
+
+
+
Welcome, please sign in.
+
+
+
+```
+
+The `ng-show` and `ng-hide` directives dynamically change out the content based on the
+authentication state, by checking to see if `firebaseUser` is not `null`. The sign in and sign out
+methods were bound directly from the view using `ng-click`.
+
+
+## User-Based Security
+
+Authenticating users is only one piece of making an application secure. It is critical to configure
+Security and Firebase Rules before going into production. These declarative rules dictate when and
+how data may be read or written.
+
+Within our [Firebase and Security Rules](https://firebase.google.com/docs/database/security/), the
+predefined `auth` variable is `null` before authentication takes place. Once a user is authenticated,
+it will contain the following attributes:
+
+| Key | Description |
+|-----|-------------|
+| `uid` | A user ID, guaranteed to be unique across all providers. |
+| `provider` | The authentication method used (e.g. "anonymous" or "google.com"). |
+| `token` | The contents of the authentication token (an OpenID JWT). |
+
+The contents of `auth.token` will contain the following information:
+
+```
+{
+ "email": "foo@bar.com", // The email corresponding to the authenticated user
+ "email_verified": false, // Whether or not the above email is verified
+ "exp": 1465366314, // JWT expiration time
+ "iat": 1465279914, // JWT issued-at time
+ "sub": "g8u5h1i3t51b5", // JWT subject (same as auth.uid)
+ "auth_time": 1465279914, // When the original authentication took place
+ "firebase": { // Firebase-namespaced claims
+ "identities": { // Authentication identities
+ "github.com": [ // Provider
+ "8513515" // ID of the user on the above provider
+ ]
+ }
+ }
+}
+```
+
+We can then use the `auth` variable within our rules. For example, we can grant everyone read access
+to all data, but only write access to their own data, our rules would look like this:
+
+```js
+{
+ "rules": {
+ // public read access
+ ".read": true,
+ "users": {
+ "$uid": {
+ // write access only to your own data
+ ".write": "$uid === auth.uid",
+ }
+ }
+ }
+}
+```
+
+For a more in-depth explanation of this important feature, check out the web guide on
+[user-based security](https://firebase.google.com/docs/database/security/user-security).
+
+
+## Authenticating With Routers
+
+Checking to make sure the client has authenticated can be cumbersome and lead to a lot of `if` /
+`else` logic in our controllers. In addition, apps which use authentication often have issues upon
+initial page load with the signed out state flickering into view temporarily before the
+authentication check completes. We can abstract away these complexities by taking advantage of the
+`resolve()` method of Angular routers.
+
+AngularFire provides two helper methods to use with Angular routers. The first is
+[`$waitForSignIn()`](/docs/reference.md#waitforsignin)
+which returns a promise fulfilled with the current authentication state. This is useful when you
+want to grab the authentication state before the route is rendered. The second helper method is
+[`$requireSignIn()`](/docs/reference.md#requiresignin)
+which resolves the promise successfully if a user is authenticated and rejects otherwise. This is
+useful in cases where you want to require a route to have an authenticated user. You can catch the
+rejected promise and redirect the unauthenticated user to a different page, such as the sign in page.
+Both of these methods work well with the `resolve()` methods of `ngRoute` and `ui-router`.
+
+### `ngRoute` Example
+
+```js
+// for ngRoute
+app.run(["$rootScope", "$location", function($rootScope, $location) {
+ $rootScope.$on("$routeChangeError", function(event, next, previous, error) {
+ // We can catch the error thrown when the $requireSignIn promise is rejected
+ // and redirect the user back to the home page
+ if (error === "AUTH_REQUIRED") {
+ $location.path("/home");
+ }
+ });
+}]);
+
+app.config(["$routeProvider", function($routeProvider) {
+ $routeProvider.when("/home", {
+ // the rest is the same for ui-router and ngRoute...
+ controller: "HomeCtrl",
+ templateUrl: "views/home.html",
+ resolve: {
+ // controller will not be loaded until $waitForSignIn resolves
+ // Auth refers to our $firebaseAuth wrapper in the factory below
+ "currentAuth": ["Auth", function(Auth) {
+ // $waitForSignIn returns a promise so the resolve waits for it to complete
+ return Auth.$waitForSignIn();
+ }]
+ }
+ }).when("/account", {
+ // the rest is the same for ui-router and ngRoute...
+ controller: "AccountCtrl",
+ templateUrl: "views/account.html",
+ resolve: {
+ // controller will not be loaded until $requireSignIn resolves
+ // Auth refers to our $firebaseAuth wrapper in the factory below
+ "currentAuth": ["Auth", function(Auth) {
+ // $requireSignIn returns a promise so the resolve waits for it to complete
+ // If the promise is rejected, it will throw a $routeChangeError (see above)
+ return Auth.$requireSignIn();
+ }]
+ }
+ });
+}]);
+
+app.controller("HomeCtrl", ["currentAuth", function(currentAuth) {
+ // currentAuth (provided by resolve) will contain the
+ // authenticated user or null if not signed in
+}]);
+
+app.controller("AccountCtrl", ["currentAuth", function(currentAuth) {
+ // currentAuth (provided by resolve) will contain the
+ // authenticated user or throw a $routeChangeError (see above) if not signed in
+}]);
+
+app.factory("Auth", ["$firebaseAuth",
+ function($firebaseAuth) {
+ return $firebaseAuth();
+ }
+]);
+```
+
+### `ui-router` Example
+
+```js
+// for ui-router
+app.run(["$rootScope", "$state", function($rootScope, $state) {
+ $rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
+ // We can catch the error thrown when the $requireSignIn promise is rejected
+ // and redirect the user back to the home page
+ if (error === "AUTH_REQUIRED") {
+ $state.go("home");
+ }
+ });
+}]);
+
+app.config(["$stateProvider", function ($stateProvider) {
+ $stateProvider
+ .state("home", {
+ // the rest is the same for ui-router and ngRoute...
+ controller: "HomeCtrl",
+ templateUrl: "views/home.html",
+ resolve: {
+ // controller will not be loaded until $waitForSignIn resolves
+ // Auth refers to our $firebaseAuth wrapper in the factory below
+ "currentAuth": ["Auth", function(Auth) {
+ // $waitForSignIn returns a promise so the resolve waits for it to complete
+ return Auth.$waitForSignIn();
+ }]
+ }
+ })
+ .state("account", {
+ // the rest is the same for ui-router and ngRoute...
+ controller: "AccountCtrl",
+ templateUrl: "views/account.html",
+ resolve: {
+ // controller will not be loaded until $requireSignIn resolves
+ // Auth refers to our $firebaseAuth wrapper in the factory below
+ "currentAuth": ["Auth", function(Auth) {
+ // $requireSignIn returns a promise so the resolve waits for it to complete
+ // If the promise is rejected, it will throw a $stateChangeError (see above)
+ return Auth.$requireSignIn();
+ }]
+ }
+ });
+}]);
+
+app.controller("HomeCtrl", ["currentAuth", function(currentAuth) {
+ // currentAuth (provided by resolve) will contain the
+ // authenticated user or null if not signed in
+}]);
+
+app.controller("AccountCtrl", ["currentAuth", function(currentAuth) {
+ // currentAuth (provided by resolve) will contain the
+ // authenticated user or throw a $stateChangeError (see above) if not signed in
+}]);
+
+app.factory("Auth", ["$firebaseAuth",
+ function($firebaseAuth) {
+ return $firebaseAuth();
+ }
+]);
+```
+Keep in mind that, even when using `ng-annotate` or `grunt-ngmin` to minify code, that these tools
+cannot peer inside of functions. So even though we don't need the array notation to declare our
+injected dependencies for our controllers, services, etc., we still need to use an array and
+explicitly state our dependencies for the routes, since they are inside of a function.
+
+We have covered the three services AngularFire provides:
+[`$firebaseObject`](/docs/reference.md#firebaseobject),
+[`$firebaseArray`](/docs/reference.md#firebasearray), and
+[`$firebaseAuth`](/docs/reference.md#firebaseauth).
+In the [next section](extending-services.md) we will discuss the advanced topic of extending the
+functionality of the `$firebaseObject` and `$firebaseArray` services.
diff --git a/docs/migration/09X-to-1XX.md b/docs/migration/09X-to-1XX.md
new file mode 100644
index 00000000..ff4cdf0b
--- /dev/null
+++ b/docs/migration/09X-to-1XX.md
@@ -0,0 +1,228 @@
+# Migrating from AngularFire `0.9.x` to `1.x.x`
+
+This migration guide will walk through some of the major breaking changes with code samples and
+guidance to upgrade your existing application from AngularFire version `0.9.x` to `1.x.x`.
+
+
+## Removal of `$firebase`
+
+The largest breaking change in AngularFire `1.0.0` is the removal of the `$firebase` service. The
+service did not provide any capabilities beyond what already existed in the vanilla Firebase SDK.
+However, sometimes you do need to quickly write data from your database without first going through
+the process of creating a synchronized array or object. Let's walk through some examples of
+migrating away from the now defunct `$firebase` service.
+
+To write data to your database, we should now use the Firebase SDK's `set()` method instead of the
+removed `$set()` method.
+
+### AngularFire `0.9.X`
+```js
+app.controller("SampleCtrl", ["$scope", "$firebase",
+ function($scope, $firebase) {
+ var profileRef = new Firebase("https://.firebaseio.com/profiles/annie");
+ var profileSync = $firebase(profileRef);
+ profileSync.$set({ age: 24, gender: "female" }).then(function() {
+ console.log("Profile set successfully!");
+ }).catch(function(error) {
+ console.log("Error:", error);
+ });
+ }
+]);
+```
+
+### AngularFire `1.X.X`
+```js
+app.controller("SampleCtrl", ["$scope",
+ function($scope) {
+ var profileRef = new Firebase("https://.firebaseio.com/profiles/annie");
+ profileRef.set({ age: 24, gender: "female" }, function(error) {
+ if (error) {
+ console.log("Error:", error);
+ } else {
+ console.log("Profile set successfully!");
+ }
+ });
+ }
+]);
+```
+
+We should similarly use the Firebase SDK's `remove()` method to easily replace the `$remove()`
+method provided by the `$firebase` service.
+
+### AngularFire `0.9.X`
+```js
+app.controller("SampleCtrl", ["$scope", "$firebase",
+ function($scope, $firebase) {
+ var profileRef = new Firebase("https://.firebaseio.com/profiles/bobby");
+ var profileSync = $firebase(profileRef);
+ profileSync.$remove().then(function() {
+ console.log("Set successful!");
+ }).catch(function(error) {
+ console.log("Error:", error);
+ });
+ }
+]);
+```
+
+### AngularFire `1.X.X`
+```js
+app.controller("SampleCtrl", ["$scope",
+ function($scope) {
+ var profileRef = new Firebase("https://.firebaseio.com/profiles/bobby");
+ profileRef.remove(function(error) {
+ if (error) {
+ console.log("Error:", error);
+ } else {
+ console.log("Profile removed successfully!");
+ }
+ });
+ }
+]);
+```
+
+Replacements for the `$asArray()` and `$asObject()` methods are given below.
+
+
+## Replacement of `$asObject()` with `$firebaseObject`
+
+Due to the removal of `$firebase`, the process of creating an instance of a synchronized object has
+changed. Instead of creating an instance of the `$firebase` service and calling its `$asObject()`
+method, use the renamed `$firebaseObject` service directly.
+
+### AngularFire `0.9.X`
+```js
+app.controller("SampleCtrl", ["$scope", "$firebase",
+ function($scope, $firebase) {
+ var ref = new Firebase("https://.firebaseio.com");
+ var sync = $firebase(ref);
+ var obj = sync.$asObject();
+ }
+]);
+```
+
+### AngularFire `1.X.X`
+```js
+// Inject $firebaseObject instead of $firebase
+app.controller("SampleCtrl", ["$scope", "$firebaseObject",
+ function($scope, $firebaseObject) {
+ var ref = new Firebase("https://.firebaseio.com");
+ // Pass the Firebase reference to $firebaseObject directly
+ var obj = $firebaseObject(ref);
+ }
+]);
+```
+
+## Replacement of `$asArray()` with `$firebaseArray`
+
+Due to the removal of `$firebase`, the process of creating an instance of a synchronized array has
+changed. Instead of creating an instance of the `$firebase` service and calling its `$asArray()`
+method, use the renamed `$firebaseArray` service directly.
+
+### AngularFire `0.9.X`
+```js
+app.controller("SampleCtrl", ["$scope", "$firebase",
+ function($scope, $firebase) {
+ var ref = new Firebase("https://.firebaseio.com");
+ var sync = $firebase(ref);
+ var list = sync.$asArray();
+ }
+]);
+```
+
+### AngularFire `1.X.X`
+```js
+// Inject $firebaseArray instead of $firebase
+app.controller("SampleCtrl", ["$scope", "$firebaseArray",
+ function($scope, $firebaseArray) {
+ var ref = new Firebase("https://.firebaseio.com");
+ // Pass the Firebase reference to $firebaseArray
+ var list = $firebaseArray(ref);
+ }
+]);
+```
+
+
+## Replacement of `$inst()` with `$ref()`
+
+Due to the removal of `$firebase`, the `$inst()` methods off of the old `$FirebaseObject` and
+`$FirebaseArray` factories were no longer meaningful. They have been replaced with `$ref()` methods
+off of the new `$firebaseObject` and `$firebaseArray` services which return the underlying
+`Firebase` reference used to instantiate an instance of the services.
+
+### AngularFire `0.9.X`
+```js
+// $FirebaseObject
+var objSync = $firebase(ref);
+var obj = sync.$asObject();
+objSync === obj.$inst(); // true
+// $FirebaseArray
+var listSync = $firebase(ref);
+var list = sync.$asArray();
+listSync === list.$inst(); // true
+```
+
+### AngularFire `1.X.X`
+```js
+// $firebaseObject
+var obj = $firebaseObject(ref);
+obj.$ref() === ref; // true
+// $firebaseArray
+var list = $firebaseArray(ref);
+list.$ref() === ref; // true
+```
+
+
+## Changes to argument lists for user management methods
+
+The previously deprecated ability to pass in credentials to the user management methods of
+`$firebaseAuth` as individual arguments has been removed in favor of a single credentials argument
+
+### AngularFire `0.9.X`
+```js
+var auth = $firebaseAuth(ref);
+auth.$changePassword("foo@bar.com", "somepassword", "otherpassword").then(function() {
+ console.log("Password changed successfully!");
+}).catch(function(error) {
+ console.error("Error: ", error);
+});
+```
+
+### AngularFire `1.X.X`
+```js
+var auth = $firebaseAuth(ref);
+auth.$changePassword({
+ email: "foo@bar.com",
+ oldPassword: "somepassword",
+ newPassword: "otherpassword"
+}).then(function() {
+ console.log("Password changed successfully!");
+}).catch(function(error) {
+ console.error("Error: ", error);
+});
+```
+
+
+## Replacement of `$sendPasswordResetEmail()` with `$resetPassword()`
+
+The `$sendPasswordResetEmail()` method has been removed in favor of the functionally equivalent
+`$resetPassword()` method.
+
+### AngularFire `0.9.X`
+```js
+var auth = $firebaseAuth(ref);
+auth.$sendPasswordResetEmail("foo@bar.com").then(function() {
+ console.log("Password reset email sent successfully!");
+}).catch(function(error) {
+ console.error("Error: ", error);
+});
+```
+
+### AngularFire `1.X.X`
+```js
+var auth = $firebaseAuth(ref);
+auth.$resetPassword("foo@bar.com").then(function() {
+ console.log("Password reset email sent successfully!");
+}).catch(function(error) {
+ console.error("Error: ", error);
+});
+```
diff --git a/docs/migration/1XX-to-2XX.md b/docs/migration/1XX-to-2XX.md
new file mode 100644
index 00000000..c77d1c3f
--- /dev/null
+++ b/docs/migration/1XX-to-2XX.md
@@ -0,0 +1,64 @@
+# Migrating from AngularFire `1.x.x` to `2.x.x`
+
+This migration document covers all the major breaking changes mentioned in the [AngularFire `2.0.0`
+change log](https://github.com/firebase/angularfire/releases/tag/v2.0.0).
+
+**Note:** If you're using Angular 2 this is not the guide for you! This is for upgrading AngularFire
+`1.x.x` (for classic Angular) to AngularFire `2.x.x`. See [AngularFire 2](https://github.com/angular/angularfire2)
+to use Firebase with Angular 2.
+
+
+## Upgrade to the Firebase `3.x.x` SDK
+
+Ensure you're using a `3.x.x` version of the Firebase SDK in your project. Version `2.x.x` of the
+Firebase SDK is no longer supported with AngularFire version `2.x.x`.
+
+| SDK Version | AngularFire Version Supported |
+|-------------|-------------------------------|
+| 3.x.x | 2.x.x |
+| 2.x.x | 1.x.x |
+
+Consult the Firebase [web / Node.js migration guide](https://firebase.google.com/support/guides/firebase-web)
+for details on how to upgrade to the Firebase `3.x.x` SDK.
+
+
+## `$firebaseAuth` Method Renames / Signature Changes
+
+The `$firebaseAuth` service now accepts an optional Firebase `auth` instance instead of a Firebase
+Database reference.
+
+```js
+// Old
+$firebaseAuth(ref);
+
+// New
+$firebaseAuth();
+// Or if you need to explicitly provide an auth instance
+$firebaseAuth(firebase.auth());
+```
+
+Several authentication methods have been renamed and / or have different method signatures:
+
+| Old Method | New Method | Notes |
+|------------|------------|------------------|
+| `$authAnonymously(options)` | `$signInAnonymously()` | No longer takes any arguments |
+| `$authWithPassword(credentials)` | `$signInWithEmailAndPassword(email, password)` | |
+| `$authWithCustomToken(token)` | `$signInWithCustomToken(token)` | |
+| `$authWithOAuthPopup(provider[, options])` | `$signInWithPopup(provider)` | `options` can be provided by passing a configured `firebase.database.AuthProvider` instead of a `provider` string |
+| `$authWithOAuthRedirect(provider[, options])` | `$signInWithRedirect(provider)` | `options` can be provided by passing a configured `firebase.database.AuthProvider` instead of a `provider` string |
+| `$authWithOAuthToken(provider, token)` | `$signInWithCredential(credential)` | Tokens must now be transformed into provider specific credentials. This is discussed more in the [Firebase Authentication guide](https://firebase.google.com/docs/auth/#key_functions). |
+| `$createUser(credentials)` | `$createUserWithEmailAndPassword(email, password)` | |
+| `$removeUser(credentials)` | `$deleteUser()` | Deletes the currently signed-in user |
+| `$changeEmail(credentials)` | `$updateEmail(newEmail)` | Changes the email of the currently signed-in user |
+| `$changePassword(credentials)` | `$updatePassword(newPassword)` | Changes the password of the currently signed-in user |
+| `$resetPassword(credentials)` | `$sendPasswordResetEmail(email)` | |
+| `$unauth()` | `$signOut()` | Now returns a `Promise` |
+| `$onAuth(callback)` | `$onAuthStateChanged(callback)` | |
+| `$requireAuth()` | `$requireSignIn()` | |
+| `$waitForAuth()` | `$waitForSignIn()` | |
+
+## Auth Payload Format Changes
+
+Although all your promises and `$getAuth()` calls will continue to function, the auth payload will
+differ slightly. Ensure that your code is expecting the new payload that is documented in the
+[Firebase Authentication guide](https://firebase.google.com/docs/auth/).
diff --git a/docs/quickstart.md b/docs/quickstart.md
new file mode 100644
index 00000000..40d74423
--- /dev/null
+++ b/docs/quickstart.md
@@ -0,0 +1,222 @@
+# Quickstart | AngularFire
+
+AngularFire is the officially supported AngularJS binding for Firebase. The combination of Angular
+and Firebase provides a three-way data binding between your HTML, your JavaScript, and the Firebase
+database.
+
+
+## 1. Create an Account
+The first thing we need to do is [sign up for a free Firebase account](https://firebase.google.com/).
+A brand new Firebase project will automatically be created for you which you will use in conjunction
+with AngularFire to authenticate users and store and sync data.
+
+
+## 2. Add Script Dependencies
+
+In order to use AngularFire in a project, include the following script tags:
+
+```html
+
+
+
+
+
+
+
+
+```
+
+Firebase and AngularFire are also available via npm and Bower as `firebase` and `angularfire`,
+respectively. A [Yeoman generator](https://github.com/firebase/generator-angularfire) is also
+available.
+
+
+## 3. Initialize the Firebase SDK
+
+We'll need to initialize the Firebase SDK before we can use it. You can find more details on the
+[web](https://firebase.google.com/docs/web/setup) or
+[Node](https://firebase.google.com/docs/server/setup) setup guides.
+
+```js
+
+```
+
+
+## 4. Inject the AngularFire Services
+
+Before we can use AngularFire with dependency injection, we need to register `firebase` as a module
+in our application.
+
+```js
+var app = angular.module("sampleApp", ["firebase"]);
+```
+
+Now the `$firebaseObject`, `$firebaseArray`, and `$firebaseAuth` services are available to be
+injected into any controller, service, or factory.
+
+```js
+app.controller("SampleCtrl", function($scope, $firebaseObject) {
+ var ref = firebase.database().ref();
+ // download the data into a local object
+ $scope.data = $firebaseObject(ref);
+ // putting a console.log here won't work, see below
+});
+```
+
+In the example above, `$scope.data` is going to be populated from the remote server. This is an
+asynchronous call, so it will take some time before the data becomes available in the controller.
+While it might be tempting to put a `console.log` on the next line to read the results, the data
+won't be downloaded yet, so the object will appear to be empty. Read the section on
+[Asynchronous Operations](guide/introduction-to-angularfire.md#handling-asynchronous-operations) for more details.
+
+
+## 5. Add Three-Way, Object Bindings
+
+Angular is known for its two-way data binding between JavaScript models and the DOM, and Firebase
+has a lightning-fast, realtime database. For synchronizing simple key / value pairs, AngularFire can
+be used to *glue* the two together, creating a "three-way data binding" which automatically
+synchronizes any changes to your DOM, your JavaScript, and the Firebase database.
+
+To set up this three-way data binding, we use the `$firebaseObject` service introduced above to
+create a synchronized object, and then call `$bindTo()`, which binds it to a `$scope` variable.
+
+```js
+var app = angular.module("sampleApp", ["firebase"]);
+app.controller("SampleCtrl", function($scope, $firebaseObject) {
+ var ref = firebase.database().ref().child("data");
+ // download the data into a local object
+ var syncObject = $firebaseObject(ref);
+ // synchronize the object with a three-way data binding
+ // click on `index.html` above to see it used in the DOM!
+ syncObject.$bindTo($scope, "data");
+});
+```
+
+```html
+
+
+
+
+
+
You said: {{ data.text }}
+
+
+```
+
+
+## 6. Synchronize Collections as Arrays
+
+Three-way data bindings are amazing for simple key / value data. However, there are many times when
+an array would be more practical, such as when managing a collection of messages. This is done using
+the `$firebaseArray` service.
+
+We synchronize a list of messages into a read-only array by using the `$firebaseArray` service and
+then assigning the array to `$scope`:
+
+```js
+var app = angular.module("sampleApp", ["firebase"]);
+app.controller("SampleCtrl", function($scope, $firebaseArray) {
+ var ref = firebase.database().ref().child("messages");
+ // create a synchronized array
+ // click on `index.html` above to see it used in the DOM!
+ $scope.messages = $firebaseArray(ref);
+});
+```
+
+```html
+
+
+
+
{{ message.text }}
+
+
+
+```
+
+Because the array is synchronized with server data and being modified concurrently by the client, it
+is possible to lose track of the fluid array indices and corrupt the data by manipulating the wrong
+records. Therefore, the placement of items in the list should never be modified directly by using
+array methods like `push()` or `splice()`.
+
+Instead, AngularFire provides a set of methods compatible with manipulating synchronized arrays:
+`$add()`, `$save()`, and `$remove()`.
+
+```js
+var app = angular.module("sampleApp", ["firebase"]);
+app.controller("SampleCtrl", function($scope, $firebaseArray) {
+ var ref = firebase.database().ref().child("messages");
+ // create a synchronized array
+ $scope.messages = $firebaseArray(ref);
+ // add new items to the array
+ // the message is automatically added to our Firebase database!
+ $scope.addMessage = function() {
+ $scope.messages.$add({
+ text: $scope.newMessageText
+ });
+ };
+ // click on `index.html` above to see $remove() and $save() in action
+});
+```
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+## 7. Add Authentication
+
+Firebase provides a [hosted authentication service](https://firebase.google.com/docs/auth/) which
+offers a completely client-side solution to account management and authentication. It supports
+anonymous authentication, email / password login, and login via several OAuth providers, including
+Facebook, GitHub, Google, and Twitter.
+
+AngularFire provides a service named `$firebaseAuth` which wraps the authentication methods provided
+by the Firebase client library. It can be injected into any controller, service, or factory.
+
+```js
+app.controller("SampleCtrl", function($scope, $firebaseAuth) {
+ var auth = $firebaseAuth();
+
+ // login with Facebook
+ auth.$signInWithPopup("facebook").then(function(firebaseUser) {
+ console.log("Signed in as:", firebaseUser.uid);
+ }).catch(function(error) {
+ console.log("Authentication failed:", error);
+ });
+});
+```
+
+
+## 8. Next Steps
+
+This was just a quick run through of the basics of AngularFire. For a more in-depth explanation of
+how to use the library as well as a handful of live code examples, [continue reading the AngularFire
+Guide](guide/README.md).
+
+To deploy your Angular applications free, fast, and without fuss, do it Firebase style! Check out
+[Firebase Hosting](https://firebase.google.com/docs/hosting/) for more information.
diff --git a/docs/reference.md b/docs/reference.md
new file mode 100644
index 00000000..ee08c0cd
--- /dev/null
+++ b/docs/reference.md
@@ -0,0 +1,1551 @@
+# API Reference | AngularFire
+
+## Table of Contents
+
+* [Initialization](#initialization)
+* [`$firebaseObject`](#firebaseobject)
+ * [`$remove()`](#remove)
+ * [`$save()`](#save)
+ * [`$loaded()`](#loaded)
+ * [`$ref()`](#ref)
+ * [`$bindTo(scope, varName)`](#bindtoscope-varname)
+ * [`$watch(callback, context)`](#watchcallback-context)
+ * [`$destroy()`](#destroy)
+ * [`$resolved`](#resolved)
+* [`$firebaseArray`](#firebasearray)
+ * [`$add(newData)`](#addnewdata)
+ * [`$remove(recordOrIndex)`](#removerecordorindex)
+ * [`$save(recordOrIndex)`](#saverecordorindex)
+ * [`$getRecord(key)`](#getrecordkey)
+ * [`$keyAt(recordOrIndex)`](#keyatrecordorindex)
+ * [`$indexFor(key)`](#indexforkey)
+ * [`$loaded()`](#loaded-1)
+ * [`$ref()`](#ref-1)
+ * [`$watch(cb[, context])`](#watchcb-context)
+ * [`$destroy()`](#destroy-1)
+ * [`$resolved`](#resolved-1)
+* [`$firebaseAuth`](#firebaseauth)
+ * Authentication
+ * [`$signInWithCustomToken(authToken)`](#signinwithcustomtokenauthtoken)
+ * [`$signInAnonymously()`](#signinanonymously)
+ * [`$signInWithEmailAndPassword(email, password)`](#signinwithemailandpasswordemail-password)
+ * [`$signInWithPopup(provider)`](#signinwithpopupprovider)
+ * [`$signInWithRedirect(provider[, options])`](#signinwithredirectprovider-options)
+ * [`$signInWithCredential(credential)`](#signinwithcredentialcredential)
+ * [`$getAuth()`](#getauth)
+ * [`$onAuthStateChanged(callback[, context])`](#onauthstatechangedcallback-context)
+ * [`$signOut()`](#signout)
+ * User Management
+ * [`$createUserWithEmailAndPassword(email, password)`](#createuserwithemailandpasswordemail-password)
+ * [`$updatePassword(password)`](#updatepasswordnewpassword)
+ * [`$updateEmail(email)`](#updateemailnewemail)
+ * [`$deleteUser()`](#deleteuser)
+ * [`$sendPasswordResetEmail(email)`](#sendpasswordresetemailemail)
+ * Router Helpers
+ * [`$waitForSignIn()`](#waitforsignin)
+ * [`$requireSignIn(requireEmailVerification)`](#requiresigninrequireemailverification)
+* [`$firebaseStorage`](#firebasestorage)
+ * [`$put(file, metadata)`](#putfile-metadata)
+ * [`$putString(string, format, metadata)`](#putstringstring-format-metadata)
+ * [`$getDownloadURL()`](#getdownloadurl)
+ * [`$getMetadata()`](#getmetadata)
+ * [`$updateMetadata(metadata)`](#updatemetadatametadata)
+ * [`$delete()`](#delete)
+ * [`$toString()`](#tostring)
+ * [Upload Task](#upload-task)
+ * [`$progress(callback)`](#progresscallback)
+ * [`$complete(callback)`](#completecallback)
+ * [`$error(callback)`](#errorcallback)
+ * [`$cancel()`](#cancel)
+ * [`$pause()`](#pause)
+ * [`$snapshot()`](#snapshot)
+ * [`then(callback)`](#then)
+ * [`catch(callback)`](#catch)
+* [Extending the Services](#extending-the-services)
+ * [Extending `$firebaseObject`](#extending-firebaseobject)
+ * [Extending `$firebaseArray`](#extending-firebasearray)
+ * [Passing a Class into $extend](#passing-a-class-into-extend)
+ * [Decorating the Services](#decorating-the-services)
+ * [Creating AngularFire Services](#creating-angularfire-services)
+* [SDK Compatibility](#sdk-compatibility)
+* [Browser Compatibility](#browser-compatibility)
+
+
+## Initialization
+
+```js
+var app = angular.module("app", ["firebase"]);
+app.config(function() {
+ var config = {
+ apiKey: "", // Your Firebase API key
+ authDomain: "", // Your Firebase Auth domain ("*.firebaseapp.com")
+ databaseURL: "", // Your Firebase Database URL ("https://*.firebaseio.com")
+ storageBucket: "" // Your Cloud Storage for Firebase bucket ("*.appspot.com")
+ };
+ firebase.initializeApp(config);
+});
+```
+
+
+## $firebaseObject
+
+The `$firebaseObject` service takes an optional [`firebase.database.Reference`](https://firebase.google.com/docs/reference/js/#firebase.database.Reference) or
+[`firebase.database.Query`](https://firebase.google.com/docs/reference/js/#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).
+
+```js
+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:
+
+```js
+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.
+
+```js
+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.
+
+```js
+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.
+
+```js
+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()`:
+
+```js
+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.
+
+```js
+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.
+
+```js
+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.
+
+```html
+
+
+```
+
+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()`.
+
+```js
+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.
+
+```js
+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.
+
+```js
+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:
+
+```js
+$scope.obj = $firebaseObject(ref);
+```
+
+```html
+
+
+ ...
+
+
+
+
+ ...
+
+```
+
+
+## $firebaseArray
+
+The `$firebaseArray` service takes an optional [`firebase.database.Reference`](https://firebase.google.com/docs/reference/js/#firebase.database.Reference) or
+[`firebase.database.Query`](https://firebase.google.com/docs/reference/js/#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.
+
+``` js
+// 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
+
+
{{ item | json }}
+```
+
+The `$firebaseArray` service can also take a
+[query](https://firebase.google.com/docs/database/web/retrieve-data) to only sync
+a subset of data.
+
+``` js
+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:
+
+```js
+// JavaScript
+var list = $firebaseArray(ref);
+list[2].foo = "bar";
+list.$save(2);
+```
+
+```html
+
+
+
+
+```
+
+### $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.
+
+```js
+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.
+
+```js
+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.
+
+```js
+$scope.list = $firebaseArray(ref);
+```
+
+```html
+
+
+
+```
+
+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.
+
+```js
+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.
+
+```js
+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.
+
+```js
+// 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.
+
+```js
+// 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`.
+
+```js
+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:
+
+```js
+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.
+
+```js
+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.
+
+```js
+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: "", prevId: "" }
+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).
+
+```js
+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:
+
+```js
+$scope.list = $firebaseArray(ref);
+```
+
+```html
+
+
+ ...
+
+
+
+
+ ...
+
+```
+
+
+## $firebaseAuth
+
+AngularFire includes support for [user authentication and management](/docs/guide/user-auth.md)
+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.
+
+```js
+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](https://firebase.google.com/docs/auth/web/custom-auth).
+This function takes two arguments: an authentication token or a Firebase Secret and an object containing optional
+client arguments, such as configuring session persistence.
+
+```js
+$scope.authObj.$signInWithCustomToken("").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](https://firebase.google.com/docs/auth/web/custom-auth)
+for more details about generating your own custom authentication tokens.
+
+### $signInAnonymously()
+
+Authenticates the client using a new, temporary guest account.
+
+```js
+$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](https://firebase.google.com/docs/auth/web/anonymous-auth)
+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.
+
+```js
+$scope.authObj.$signInWithEmailAndPassword("my@email.com", "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](https://firebase.google.com/docs/auth/web/password-auth)
+for more details about email / password authentication.
+
+### $signInWithPopup(provider)
+
+Authenticates the client using a popup-based OAuth flow. This function takes a single argument: a
+a string or provider object representing the OAuth provider to authenticate with. It 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.
+
+Valid values for the string version of the argument are `"facebook"`, `"github"`, `"google"`, and
+`"twitter"`:
+
+```js
+$scope.authObj.$signInWithPopup("google").then(function(result) {
+ console.log("Signed in as:", result.user.uid);
+}).catch(function(error) {
+ console.error("Authentication failed:", error);
+});
+```
+
+Alternatively, you can request certain scopes or custom parameters from the OAuth provider by
+passing a provider object (such as `new firebase.auth.GoogleAuthProvider()`) configured with
+additional options:
+
+```js
+var provider = new firebase.auth.GoogleAuthProvider();
+provider.addScope("https://www.googleapis.com/auth/plus.login");
+provider.setCustomParameters({
+ login_hint: "user@example.com"
+});
+
+$scope.authObj.$signInWithPopup(provider).then(function(result) {
+ console.log("Signed in as:", result.user.uid);
+}).catch(function(error) {
+ console.error("Authentication failed:", error);
+});
+```
+
+Firebase currently supports [Facebook](https://firebase.google.com/docs/auth/web/facebook-login),
+[GitHub](https://firebase.google.com/docs/auth/web/github-auth),
+[Google](https://firebase.google.com/docs/auth/web/google-signin),
+and [Twitter](https://firebase.google.com/docs/auth/web/twitter-login) authentication. Refer to the
+linked documentation in the previous sentence for information about configuring each provider.
+
+### $signInWithRedirect(provider[, options])
+
+Authenticates the client using a redirect-based OAuth flow. This function takes a single argument: a
+string or provider object representing the OAuth provider to authenticate with. It 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.
+
+Valid values for the string version of the argument are `"facebook"`, `"github"`, `"google"`, and
+`"twitter"`:
+
+```js
+$scope.authObj.$signInWithRedirect("google").then(function() {
+ // Never called because of page redirect
+ // Instead, use $onAuthStateChanged() to detect successful authentication
+}).catch(function(error) {
+ console.error("Authentication failed:", error);
+});
+```
+
+Alternatively, you can request certain scopes or custom parameters from the OAuth provider by
+passing a provider object (such as `new firebase.auth.GoogleAuthProvider()`) configured with
+additional options:
+
+```js
+var provider = new firebase.auth.GoogleAuthProvider();
+provider.addScope("https://www.googleapis.com/auth/plus.login");
+provider.setCustomParameters({
+ login_hint: "user@example.com"
+});
+
+$scope.authObj.$signInWithRedirect(provider).then(function(result) {
+ // Never called because of page redirect
+ // Instead, use $onAuthStateChanged() to detect successful authentication
+}).catch(function(error) {
+ console.error("Authentication failed:", error);
+});
+```
+
+Firebase currently supports [Facebook](https://firebase.google.com/docs/auth/web/facebook-login),
+[GitHub](https://firebase.google.com/docs/auth/web/github-auth),
+[Google](https://firebase.google.com/docs/auth/web/google-signin),
+and [Twitter](https://firebase.google.com/docs/auth/web/twitter-login) authentication. Refer to the
+linked documentation in the previous sentence for information about configuring each provider.
+
+### $signInWithCredential(credential)
+
+Authenticates the client using a credential. This function takes a single argument: the credential
+object. Credential objects are created from a provider-specific set of user data, such as their
+email / password combination or an OAuth access token.
+
+```js
+// Email / password authentication with credential
+var credential = firebase.auth.EmailAuthProvider.credential(email, password);
+
+$scope.authObj.$signInWithCredential(credential).then(function(firebaseUser) {
+ console.log("Signed in as:", firebaseUser.uid);
+}).catch(function(error) {
+ console.error("Authentication failed:", error);
+});
+```
+
+```js
+// Facebook authentication with credential
+var credential = firebase.auth.FacebookAuthProvider.credential(
+ // `event` come from the Facebook SDK's auth.authResponseChange() callback
+ event.authResponse.accessToken
+);
+
+$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 `$signInWithCredential()` for the
+[email / password](https://firebase.google.com/docs/reference/node/firebase.auth.EmailAuthProvider#.credential),
+[Facebook](https://firebase.google.com/docs/reference/node/firebase.auth.FacebookAuthProvider#.credential),
+[GitHub](https://firebase.google.com/docs/reference/node/firebase.auth.GithubAuthProvider#.credential),
+[Google](https://firebase.google.com/docs/reference/node/firebase.auth.GoogleAuthProvider#.credential),
+and [Twitter](https://firebase.google.com/docs/reference/node/firebase.auth.TwitterAuthProvider#.credential)
+authentication providers. Refer to the linked documentation in the previous sentence for information
+about creating a credential for 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`.
+
+```js
+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`.
+
+```js
+$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.
+
+```js
+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.
+
+```html
+
+ {{ firebaseUser.displayName }} | Sign out
+
+```
+
+### $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.
+
+```js
+$scope.authObj.$createUserWithEmailAndPassword("my@email.com", "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.
+
+```js
+$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.
+
+```js
+$scope.authObj.$updateEmail("new@email.com").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.
+
+```js
+$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.
+
+```js
+$scope.authObj.$sendPasswordResetEmail("my@email.com").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"](/docs/guide/user-auth.md#authenticating-with-routers)
+section of our AngularFire guide for more information and a full example.
+
+### $requireSignIn(requireEmailVerification)
+
+Helper method which returns a promise fulfilled with the current authentication state if the user
+is authenticated and, if specified, has a verified email address, but otherwise rejects the promise.
+This is intended to be used in the `resolve()` method of Angular routers to prevent unauthenticated
+users from seeing authenticated pages momentarily during page load. See the
+["Using Authentication with Routers"](/docs/guide/user-auth.md#authenticating-with-routers)
+section of our AngularFire guide for more information and a full example.
+
+## $firebaseStorage
+
+AngularFire includes support for [binary storage](/docs/guide/uploading-downloading-binary-content.md)
+with the `$firebaseStorage` service.
+
+The `$firebaseStorage` service takes a [Storage](https://firebase.google.com/docs/storage/) reference.
+
+```js
+app.controller("MyCtrl", ["$scope", "$firebaseStorage",
+ function($scope, $firebaseStorage) {
+ var storageRef = firebase.storage().ref("images/dog");
+ $scope.storage = $firebaseStorage(storageRef);
+ }
+]);
+```
+
+The storage object returned by `$firebaseStorage` contains several methods for uploading and
+downloading binary content, as well as managing the content's metadata.
+
+### $put(file, metadata)
+
+[Uploads a `Blob` object](https://firebase.google.com/docs/storage/web/upload-files) to the specified storage reference's path with an optional metadata parameter.
+Returns an [`UploadTask`](#upload-task) wrapped by AngularFire.
+
+
+```js
+var htmlFile = new Blob([""], { type : "text/html" });
+var uploadTask = $scope.storage.$put(htmlFile, { contentType: "text/html" });
+```
+
+### $putString(string, format, metadata)
+
+[Uploads a raw, `base64` string, or `base64url` string](https://firebase.google.com/docs/storage/web/upload-files#upload_from_a_string) to the specified storage reference's path with an optional metadata parameter.
+Returns an [`UploadTask`](#upload-task) wrapped by AngularFire.
+
+```js
+var base64String = "5b6p5Y+344GX44G+44GX44Gf77yB44GK44KB44Gn44Go44GG77yB";
+// Note: valid values for format are "raw", "base64", "base64url", and "data_url".
+var uploadTask = $scope.storage.$putString(base64String, "base64", { contentType: "image/gif" });
+```
+
+### $getDownloadURL()
+
+Returns a promise fulfilled with [the download URL](https://firebase.google.com/docs/storage/web/download-files#download_data_via_url) for the file stored at the configured path.
+
+```js
+$scope.storage.$getDownloadURL().then(function(url) {
+ $scope.url = url;
+});
+```
+
+### $getMetadata()
+
+Returns a promise fulfilled with [the metadata of the file](https://firebase.google.com/docs/storage/web/file-metadata#get_file_metadata) stored at the configured path. File
+metadata contains common properties such as `name`, `size`, and `contentType`
+(often referred to as MIME type) in addition to some less common ones like `contentDisposition` and `timeCreated`.
+
+```js
+$scope.storage.$getMetadata().then(function(metadata) {
+ $scope.metadata = metadata;
+});
+```
+
+### $updateMetadata(metadata)
+
+[Updates the metadata of the file](https://firebase.google.com/docs/storage/web/file-metadata#update_file_metadata) stored at the configured path.
+Returns a promise fulfilled with the updated metadata.
+
+```js
+var updateData = { contenType: "text/plain" };
+$scope.storage.$updateMetadata(updateData).then(function(updatedMetadata) {
+ $scope.updatedMetadata = updatedMetadata;
+});
+```
+
+### $delete()
+
+Permanently [deletes the file stored](https://firebase.google.com/docs/storage/web/delete-files) at the configured path. Returns a promise that is resolved when the delete completes.
+
+```js
+$scope.storage.$delete().then(function() {
+ console.log("successfully deleted!");
+});
+```
+
+### $toString()
+
+Returns a [string version of the bucket path](https://firebase.google.com/docs/reference/js/firebase.storage.Reference#toString) stored as a `gs://` scheme.
+
+```js
+// gs://///