The following post is going to give a high level view of the AngularFire library, and how it works under the hood. I’m going to walkthrough a simple grocery list app I wrote, and show how using AngularFire makes things seem to work like magic.

I’ve been working a lot recently with Google’s Firebase to build out a few side projects, and I wanted to write about my experience. The Firebase Platform is really powerful, and provides services that cover most of your application needs. It has services for Hosting, Authentication, Database Storage, File Storage, and more. In order to use these services, you can build out a Node Express setup or even make use of Firebase Cloud Functions and build out an API….or there’s an even easier method to do this with the AngularFire Library.

The following post is going to give a high level view of the AngularFire library, and how it works under the hood. I’m going to walkthrough a simple grocery list app I wrote, and show how using AngularFire makes things seem to work like magic.

Please note that this post assumes that you are already familiar with Firebase applications and the Firebase console. For a more in depth walkthrough please checkout my post on RhythmAndBinary.com here and also my Angular-In-Depth post on deployment here.

AngularFire

AngularFire is the official library that connects Angular to Firebase. As of this writing, AngularFire provides connections to the following Firebase services:

  1. Authentication
  2. File Storage
  3. Cloud Firestore (NoSQL Database)

The Authentication Service provides OAuth through multiple providers. The following screenshot shows what is available in the console once you setup your Firebase App:

AngularFire provides realtime updates through observable streams and is highly optimized. Under the hood AngularFire is also using a custom protocol called “WebChannel” which operates to create the real time synchronization (check out the link here for more info). There are also action API endpoints that integrate nicely with NgRx if you wanted to use that in conjunction with AngularFire as well.

AngularFire also has a great README that walks you through a lot of the basics from the initial installation to actual code examples for different services. Checkout their README here.

In the remainder of the post, I’m going to go through using Authentication and Cloud Firestore. The File Storage feature is also really great and well documented here.

Firebase Groceries

(screenshot of the grocery list app we’re going to review)

In order to showcase AngularFire, I’m going to refer to a basic grocery list app that can be viewed here. You can also follow along and view the source code on GitHub here.

The app is very basic and has dialogs for creating an account and logging in. The data that is stored with Cloud Firestore is keyed on user data specifically to enhance security (more on that later).

The sample app only has one component. All of the code snippets that I show below are all from the same grocery-list.component.ts and grocery-list.component.html files.

I’m going to focus on AngularFire Authentication and Cloud Firestore. I’m going to present how to connect those services to your app, and then some notes on the internals and how it all works (like magic).

Authentication

Everyone has to deal with authentication for apps. Obviously, the degree to which you go about it depends on what you are storing. Firebase makes authentication super easy to work with by providing the Authentication service. This saves you time and effort so you don’t have to implement a provider server side. By just injecting the AngularFireAuth object into your app, you immediately get access to the Firebase Authentication service, and are up and running very quickly.

So let’s get to the code!

First, you need to enable authentication in your Firebase project. In the Firebase console, make sure to go over to authentication and turn it “on” by selecting a Sign-in method. Firebase allows you to use multiple OAuth providers to include Google, Facebook, and several others. For the project I’m showcasing, I’m just using the generic email/password provider.

Once you’ve enabled it (and added the endpoint to your environment.ts file) you can connect it to your project by injecting an instance of AngularFireAuth into your component’s constructor. Here’s what I did for my grocery list app:

constructor(public afs: AngularFirestore, public afAuth: AngularFireAuth) {
    this.afAuth.auth.onAuthStateChanged(user => {
      if (user) {
        // show email in welcome message
        this.email = user.email;
        // call method that selects when authenticated
        this.selectItems(user.uid);
      }
    });
  }

Additionally, if you notice I make a call to onAuthStateChanged to check if there is a user logged in. The onAuthStateChanged call returns an observable stream which has auser object that can be interrogated. This user object will be defined if the person using the app has been authenticated. You can use the basic if statement here to determine if someone is logged in. This is particularly useful if someone hits refresh on the page or navigates away from the main page. As soon as the component is generated, it checks to see if the user is authenticated, if so then data is selected with this.selectItems(user.uid).

I also inject the AngularFirestore object here, but ignore that for now since that’s part of the discussion later.

Authenticating with AngularFireAuth

The AngularFireAuth object has a method that allows you to easily create users within your application. In the grocery list app, I did the following:

createUser(email: string, password: string) {
    this.afAuth.auth.createUserWithEmailAndPassword(email, password)
      .then(() => {
          // on success hide form and store variables in login
          // and then call the login method
          this.showCreateUserInputForm = false;
          this.loginUser(email, password);
      })
      .catch((error) => {
        alert(error);
      });
  }

Ultimately, the way the user is created is by a simple call to afAuth.auth.createUserWithEmailAndPassword. All that has to be passed in here is just a username and password, and the method will return a Promise object with a success or error message. The AngularFireAuth module automatically does basic checks for things like unique username and others that you would normally enforce with boiler plate code.

There is also a method in the AngularFireAuth module that authenticates (logs in) users. Here is what I did for the grocery list app:

loginUser(email: string, password: string) {
    this.afAuth.auth.signInWithEmailAndPassword(email, password)
      .then(() => {
        // on success populate variables and select items
        this.selectItems(this.afAuth.auth.currentUser.uid);
      })
      .catch((error) => {
        alert(error);
      });
  }

As you see here, once you’ve created a user then they can authenticate with a call to afAuth.auth.signInWithEmailAndPassword. Just like for the create method call, you just pass an email and password here. The AngularFireAuth module handles the verification of the email and password all for you, without you having to write out boiler plate code to handle the various conditions etc. Even the error response messages are fairly easy to understand and could be shown to the users directly.

Finally, when your user is ready to leave the application there is a basic signOut method that handles that process. For the grocery list app, I used that here:

// async is not necessary here, just controlling the event loop
  async logoutUser() {
    await this.afAuth.auth.signOut()
      .catch(function(error) { alert(error); });

    this.email = '';
    this.password = '';
    this.showLoginUserInputForm = false;
    this.showCreateUserInputForm = false;
  }

Connecting AngularFireAuth to the View

So this is all great for the component code, but what does it look like in the template?

The great part about AngularFire is that the observable stream’s that are fed back can just make use of the async pipe. With some use of the *ngIf and *ngFor directives, your data can be shown in the view without a lot of extra work. No more use of flags or various conditional checks, just using the directives bound to the observables and the hide/show logic is handled for you.

Let’s start with the “login” dialog. I used the async pipe with the AngularFireAuth object in the grocery list app as follows:

<div class="container">
  <div class="row add-item" *ngIf="afAuth.user | async as user; else showLogin">
    <div class="container">
      <div class="row">
        <div class="col-sm welcome-display">
          <h1>Welcome {{ email }}!</h1>
        </div>
          ...

As you can see, the *ngIf checks on the state of the user observable with the async pipe to determine when to show content when a user is logged in. This is really convenient because with this one line of code, your application is setup to show or not show specific places based on the user being authenticated. Without this setup, you’d have to have flags or some version of a dialog with components to handle this same type of behavior. The observable stream’s that are returned from the Authentication service make this whole process super easy.

I also want to note that the Firebase Authentication service has a free pop-up that lets your users authenticate with Google. You can call this popup with your code by following the documentation here.

There are a lot of other great parts about the AngularFireAuth module, and I recommend reviewing the official documents when you get some time.

Cloud Firestore

Cloud Firestore is a NoSQL database provided by Firebase. There is a realtime data sync provided by this service that makes updates almost immediate. Using AngularFire, you also connect the various database operations to dispatch events similar to NgRx. These dispatch event operate similar to the way a reducer and data store control state in an NgRx application. The effect that is provided here is basically control of remote state, whereby updates are pushed to Cloud Firestore and then streamed back to any consumers.

The way the data is stored is through documents and collections. Each record in the NoSQL database is considered a “document” that is connected to another “document” via a key value pair. This is obviously different than a relational database which stores data in columns and rows. When working with this data in your code you create observablecollection and document objects which stream updates directly to your app from the database. You can also review the data visually in the console after it gets created if you need to.

My grocery list app uses documents which takes some explanation. First, I’m just going to show an example with a collection. The instructions on the AngularFire README show creating collection objects with the following:

(click here for picture source)

If you look at the example here, the items object is an observable which reacts to any database changes in realtime using the valueChanges() observable stream. This is done with Redux compatible code and websockets under the hood.

The example in the README continues by showing how the async pipe can be used to show the database’s information directly with *ngFor as follows:

Since the items object is an observable stream, any updates are immediately shown within your application. This is really powerful because all of the hard work with websockets and state management is done for you.

Cloud Firestore Document Objects

For my grocery list app, I first select a document and then reference a nested collection. This is a little more complicated, but I did this because I keyed the data based on userIds (more on this later). Here is how I did that in the grocery list app:

selectItems(uid: string) {
    this.groceryItemsDoc = this.afs.doc<Item>('user/' + uid);
    this.groceryItems = this.groceryItemsDoc.collection<GroceryItem>('GroceryItems').valueChanges();
    
    // turn on logging to see how requests are sent
    // this.groceryItemsDoc.collection<GroceryItem>('GroceryItems').auditTrail().subscribe(console.log);
  }

As you can see, I first select a specific document keyed on the userId, and then subscribe to the valueChanges() observable stream of the “GroceryItems” collection within the document. This follows the same model as the example from the README except its a nested collection. Here valueChanges() are specific to the “GroceryItems” collection that is stored within the document created for the userId of the authenticated user. I’m going to into more about why I did this below, but this approach is documented in the angularFire2 README here.

Interacting with the Cloud Firestore database with regular CRUD operations is also super simple. AngularFire has set, update, and delete method that dispatches changes directly to your app’s database instance. This makes basic operations easy to use in your code as you see with the addItem() and deleteItem() methods of my grocery list app here:

// async is not necessary here, but using it to control event loop
  async addItem() {
    const id = this.afs.createId();
    const groceryItem: GroceryItem = {
      value: this.createItem,
      id: id
    };

    await this.groceryItemsDoc.collection<GroceryItem>('GroceryItems').doc(id).set(groceryItem)
      .then(() => {
          // when successful clear input field value here
          this.createItem = '';
      })
      .catch((error) => {
        alert(error);
      });
  }

  // async is not necessary here, but using it to control event loop
  async deleteItem(groceryItem: GroceryItem) {
    await this.groceryItemsDoc.collection<GroceryItem>('GroceryItems').doc(groceryItem.id).delete()
      .catch((error) => { alert(error); });
  }

In order to call the methods you just need the object you want to change, and to pass in an id value that Cloud Firestore uses to keep each record unique. The operation returns a promise, which is easily handled with your code as you see above. For more info on these operations (and examples) checkout the README here.

Viusalizing Cloud Firestore Operations

Also, I used a subscription toauditTrail to be able to show in the console the read and writes that are streamed in the console. This use of auditTrail shows how AngularFire’s setup is very similar to the way NgRx uses dispatches for state management. Turning on auditTrail gives you a nice way to see how the remote state is being maintained with the observables as seen in the console here (note the add and removed):

Finally, when you want to show Cloud Firestore data in your view template, you just reference the observable similar to the way the AngularFireAuth object did earlier. Here is how I did it with the grocery list app:

...
      <div class="row display-item">
        <div class="col-sm">
          <div *ngFor="let groceryItem of groceryItems | async">
            <div class='grocery-display'>
              <div class="delete-button"><button type="button" class="btn btn-danger round-button" (click)="deleteItem(groceryItem)">X</button></div>
              <div class="grocery-item">
                <div class="name-display">{{ groceryItem.value }}</div>
              </div>
            </div>
          </div>
        </div>
      </div>
...

Here I’m referencing the valueChanges() observable so that I still get the same binding that I can use with the async pipe. Any updates to the data is updated in realtime as the remote state is streamed to the observable created with the valueChanges() here. There is also an observable stream for snapshotChanges() which handles the use case for when you just want to render a view. Checkout more on snapshotChanges() here.

Security

I want to now explain why I keyed the data based on the userId in Cloud Firestore earlier. Since the environment.ts file that is deployed with your app is exposed, anyone can see the database endpoint for your Cloud Firestore instance. If hackers looked at the source code for your page, they could see the endpoint and then attempt to retrieve data with the console in the browser. The way to stop this (and secure the data) is to build out rules in the database for what operations are allowed by who.

When you first setup your Firebase Cloud Firestore instance, you can either set it up with “testing” mode (where its completely open). After you’re ready, you can lock this down with custom matching rules built right into the console.

Firebase recommends building out rules to control how reads are done. If you key the data based on userId of the database requests then it means that reads of data can only be done by the user that created them in an authenticated session. You do this by going into the console and setting the value in the “Rules” tab, as you see here:

If you notice the rules that have been setup, it’s basically just matching with regular expressions. Whenever you use curly braces {} you expose a value as a variable that can be interrogated (more on that in a second). The values for each match are traversing the documents/collections that makeup the NoSQL database instance.

The first match to /databases/{database}/documents is walking through the nested collections of the NoSQL database, and applies to any document in the database instance connected to your application.

The second match to /user/{userId}/GroceryItems/{document=**} is saying any document selected within the user->userId->GroceryItems collection. If you notice the use of {userId} with the curly braces “{}” makes the value of userId available as a variable within the console (same thing is done with {database} also). We’re going to interrogate that value to enforce security next.

Within the second match you notice the

allow read, create, update, delete: if request.auth.uid == userId;

This is the first rule within the matches that says “any read, update or delete operations are allowed if the authorized userId in the session matches the userId variable”. This userId variable is the one that you exposed with the curly braces “{}”. This is really significant because it allows you to isolate permissions within this collection so that a user’s data can only be read by that user.

The Firebase documentation has more information on how to make more rules here.

Wrapping Up

As I mentioned before, all of this “magic” is made possible using Observable Streams, and using bi-direction near-instant communication channels. AngularFire follows a similar model to the NgRx implementation with controlling remote state, but without a formal reducer. The observable stream endpoints like valueChanges() receive state updates to the database in realtime. These updates are sent to any instances running your app. So anywhere that is hosting your app’s data stays in sync with remote data stored in Cloud Firestore. This all translates to making your life as a developer much easier since you only have to subscribe to the observable streams in your application. With the async pipe you can control what is shown, and your data is updated in realtime without having to manually poll the data source for updates.

One of the cowriters of AngularFire (David East) gave a good talk that explains all of this in much greater detail. Special thanks to Nicholas Jamieson from Angular-In-Depth for sending me the link here. Watch David’s talk for some good visuals and a better understanding of how AngularFire works in comparison to NgRx. Hope this post has helped to give a good start with AngularFire, and it has helped you start to use it with your projects.