Part Two: Angular with Firestore — Intro

Read more below.

Part Two: Angular with Firestore — Intro

10 min read · Ryan Jones

Series Breakdown:

Thank you and enjoy! Github code, here.

What we will cover:

  • Overview of Firestore and the AngularFire library
  • Creating collection and documents
  • Overview of Firestore Data Model
  • Basic queries with AngularFire
  • Angular + TypeScript key concepts in the code
  • Deploying to Firebase Hosting

Please, follow along with Part One: Angular with Firebase Hosting before working through Part Two as this is a continuous series and some concepts will not be recovered in detail.

Prerequisites:

  • Google Account
  • Firebase project created (check article here)
  • Basic web development knowledge
  • Basic NoSQL knowledge

What is Firestore?

Firestore is a NoSQL database which is offered through Firebase. It’s an upgraded version of the Firebase Realtime Database with everything the Firebase Realtime Database offers and much more.

Features:

  • Flexible and scalable NoSQL document-oriented database
  • Supports mobile, web, and server development
  • Keeps data in sync across apps in realtime
  • Offline support with offline reads and writes
  • Seamless integration with Firebase and GCP products
  • Cloud Function triggers off Firestore create/update/delete/write

Source

What is AngularFire?

AngularFire is an npm package which makes it easier to work with Firebase products when building Angular applications.

Features:

  • Observable based — Use the power of RxJS, Angular, and Firebase.
  • Realtime bindings — Synchronize data in realtime.
  • Authentication — Log users in with a variety of providers and monitor authentication state in realtime.
  • Offline Data — Store data offline automatically with AngularFirestore.
  • ngrx friendly — Integrate with ngrx using AngularFire’s action based APIs.

Source

Firestore Data Model:

Firestore has the concept of collections and documents. You can think of a Collection as an array of objects. Where a document is a single object in that array (collection).

Collection:

A collection can be thought of as an array of objects. In this example, we have a collection of articles and each document is a single article.

Example:

    articles = [
     {
       "title": "Fires everywhere!",
       "description": "California is on fire.. Run for the hills"
       "id": "123ABC"
     },
     {
       "title": "Are Colleges worth the debt?",
       "description": "A comprehensive breakdown of the College debt crisis."
       "id": "456DEF"
     }
    ]

Document:

A document is a single object in the array of objects. Below, we can see the properties of a single document from the collection of articles.

Example:

    {
     "title": "Are Colleges worth the debt?",
     "description": "A comprehensive breakdown of the College debt crisis."
     "id": "456DEF"
    }

Want to dive deeper into modeling data with Firestore?

Check out our article on the Firestore Data Model. Where we cover different ways of modeling your data such as one-to-one, one-to-many, many-to-many. We also cover data duplication and root vs sub collections to improve read performance.

Mocking data in Firestore

Below, we are going to mock out some data for our Angular application. Navigate to the Firebase Console.

Create Firestore database

Create Firestore database

Set security rules to test mode

Set security rules to test mode Set security rules to test mode

Create articles collection

Create articles collection Create articles collection

Create initial article

Create first document Create first document

Create second document

Create second document Create second document

View documents

View documents View documents

Dope we have our initial data set up. Now lets setup our Angular project with AngularFire and write some code to load our data in our application.

Adding Firestore in Angular App

Below, we are going to install Firebase and AngularFire2, setup our credentials file, create a few components, and write some AngularFire code to support reading data from the database.

Install Firebase and AngularFire2

You can find the official installation documentation for AngularFire2, on their GitHub.


    $myapp: npm install firebase angularfire2 --save

Copy Credentials for Firestore

Copy Credentials for Firestore

Open Angular environment file

You can find this file at src/environments/environment.ts. Once you have the config object from the screenshot above copied. Swap out the code snippet below with your real values.


    export const environment = {
     production: false,
     firebaseConfig: {
       apiKey: "AIzaSyDjPB_xxxxxxxxxxxxx",
       authDomain: "myapp-xxxxxx.firebaseapp.com",
       databaseURL: "https://myapp-xxxxxxx.firebaseio.com",
       projectId: "myapp-xxxxxxx",
       storageBucket: "myapp-xxxxxxx.appspot.com",
       messagingSenderId: "xxxxxxxxxx"
     }
    };

Update App Module file

We also need to update our src/app/app.module.ts file so that our Angular app uses our credentials with the AngularFire library.


    import { environment } from '../environments/environment';
    export const firebaseConfig = environment.firebaseConfig;

    import { AngularFireModule } from 'angularfire2';
    import { AngularFirestoreModule } from 'angularfire2/firestore';
    ...
    @NgModule({
     ...
     imports: [
       AngularFireModule.initializeApp(firebaseConfig),
       AngularFirestoreModule
     ]
    })

Here is the full App Module file


    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';
    import { HomeComponent } from './home/home.component';
    import { ErrorComponent } from './error/error.component';
    import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
    import { ArticlesComponent } from './articles/articles.component';
    import { ArticleComponent } from './article/article.component';
    import { environment } from '../environments/environment';
    export const firebaseConfig = environment.firebaseConfig;
    import { AngularFireModule } from 'angularfire2';
    import { AngularFirestoreModule } from 'angularfire2/firestore';
    @NgModule({
     declarations: [
       AppComponent,
       HomeComponent,
       ErrorComponent,
       ArticlesComponent,
       ArticleComponent
     ],
     imports: [
       BrowserModule,
       AppRoutingModule,
       NgbModule.forRoot(),
       AngularFireModule.initializeApp(firebaseConfig),
       AngularFirestoreModule
     ],
     providers: [],
     bootstrap: [AppComponent]
    })
    export class AppModule { }

Now everything should be connected. So lets add the components which will actually interact with Firestore.

Create Components

We need components to support showing all of our articles and individual articles based on different routes. To support this we will create two components, articles and article.


    $myapp: ng generate component articles
    $myapp: ng generate component article

Update Routing

To give users the ability to navigate to our articles and article components we need to add the following code into our app-routing.module.ts file.


    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    import { ErrorComponent } from './error/error.component';
    import { HomeComponent } from './home/home.component';
    import { ArticlesComponent } from './articles/articles.component';
    import { ArticleComponent } from './article/article.component';
    const routes: Routes = [
     { path: '', component: HomeComponent },
     { path: 'articles', component: ArticlesComponent },
     { path: 'articles/:id', component: ArticleComponent },
     { path: '\*\*', component: ErrorComponent }
    ];
    @NgModule({
     imports: [RouterModule.forRoot(routes)],
     exports: [RouterModule]
    })
    export class AppRoutingModule { }

The path articles/:id is a way to dynamically pass an id value into the route. Then we can access this id to request the document from Firestore.

Articles Component

We need to add HTML and TypeScript for our component to work properly with Firestore.

HTML (src/app/articles/articles.component.html):

There may be some Angular code that you don’t recognize depending on your level of experience. However, here a couple of key concepts in the code snippet below.

  • routerLink — think of it like a powerful href
  • *ngIf — simple way of evaluating a boolean value then hiding or showing some html
  • *ngFor — loop over array (e.g. firestore collection)

<div>
 <h1>Articles List</h1>
 <p>Below is a list of dynamic article links</p>
<div class="row" ng-if="i % 3 === 0">
   <div class="col-sm-4" \*ngFor="let article of articles$ | async; index as i">
     <div class="thumbnail">
       <a [routerLink]="['/articles', article.id]">
         <img [src]="article.img" [alt]="article.title" style="width:100%">
         <div class="caption">
           <p>{{ article.description}}</p>
         </div>
       </a>
     </div>
   </div>
 </div>
</div>

So we are saying loop over the Firestore collection of articles asynchronously (\*ngFor="let article of articles$ | async"). Then for each article create some basic HTML with a link the user can click to view that article.

TypeScript (src/app/articles/articles.component.ts):
  • Observable — pub/sub paradigm for event handling, async programming, and handling multiple values
  • Interface — defining the data coming back from Firestore
  • snapShotChanges — listen for Firestore data changes in realtime and get back document data + meta data (e.g. id)
  • subscribe — triggers our Observable so we can see what Firestore returns

    import { Component, OnInit } from '@angular/core';
    import { AngularFirestore } from 'angularfire2/firestore';
    import { Observable } from 'rxjs';
    import { map } from 'rxjs/operators';
    export interface Article {
     title: string;
     img: string;
     description: string;
    }
    @Component({
     selector: 'app-articles',
     templateUrl: './articles.component.html',
     styleUrls: ['./articles.component.css']
    })
    export class ArticlesComponent implements OnInit {
     // Observable which will hold an array of Article
     articles$: Observable<Article[]>;
     constructor(private db: AngularFirestore) {
     // The code below will query all the articles
     // and return id + data (e.g. title, description, img)
       this.articles$ = this.db.collection<Article>('articles')
         .snapshotChanges().pipe(
           map(actions => actions.map(a => {
             const data = a.payload.doc.data() as Article;
             const id = a.payload.doc.id;
             return { id, ...data };
           }))
         );
     }
     ngOnInit() {
       this.articles$.subscribe(data => console.log(data));
     }
    }

Article Component

We need to add HTML and TypeScript for our component to work properly with Firestore.

HTML (src/app/article/article.component.html):

Below we check \*ngIf the article exists then load it using | async pipe. We then show the title, image, and description to the screen using {{ value }} templating.


<div \*ngIf="article$ | async as article">
 <h1>{{ article.title }}</h1>
 <img [src]="article.img" class="img-responsive"  style="max-width: 50%; max-height: 50%;" [alt]="article.title">
 <p>{{ article.description }}</p>
</div>
TypeScript (src/app/article/article.component.ts):
  • AngularFire — importing AngularFire2
  • ActivatedRoute — importing @angular/router to get id from route
  • valueChanges — listen for Firestore changes in realtime and get back data

    import { Component, OnInit } from '@angular/core';
    import { AngularFirestore } from 'angularfire2/firestore';
    import { Observable } from 'rxjs';
    // listen to changes in the router
    import { ActivatedRoute } from '@angular/router';
    // define Article type
    export interface Article {
     title: string;
     img: string;
     description: string;
    }
    @Component({
     selector: 'app-article',
     templateUrl: './article.component.html',
     styleUrls: ['./article.component.css']
    })
    export class ArticleComponent implements OnInit {
     article$: Observable<Article>;   // Observable of type Article
     id: String;   // will hold id passed through route (:id)
     constructor(
       private db: AngularFirestore,
       private route: ActivatedRoute
     ) {
       // set 'id' when page loads from route params.id
       this.route.params.subscribe(params => this.id = params.id)
     }
     ngOnInit() {
       // query Firestore using 'id' when page loads
       this.article$ = this.db
           .doc<Article>('articles/' + this.id)
           .valueChanges();
     }
    }

We query db.doc for <Article> with id of articles/<document_id> and listen in realtime to valueChanges Then set the response to article$ to update the html.

Deploy Locally

Now that we have our code in place lets deploy it locally so that we can test the connections before pushing it live to Firebase Hosting.


    $myapp: ng serve -o

Missing Navigation

Firebase project welcome screen

As you can see we only have navigation for home and error. Lets update this in the src/app/app.component.html file. Optional, remove the error navigation option as well.


<div>
 <nav class="navbar navbar-expand-md navbar-dark bg-dark">
   <a class="navbar-brand" href="#">My App</a>
   <div class="collapse navbar-collapse">
     <ul class="navbar-nav">
       <li class="nav-item">
         <a class="nav-link" routerLink="">Home</a>
       </li>
       <li class="nav-item">
         <a class="nav-link" routerLink="/articles">Articles</a>
       </li>
     </ul>
   </div>
 </nav>
 <div class="container">
   <!--Router loads component based on url here-->
   <router-outlet></router-outlet>
 </div>
</div>

Switch over the the browser and you should see the change already took place. This is due to the local server listening to changes and auto-updating.

localhost:4200/articles localhost:4200/articles

Click an article

localhost:4200/articles/:id localhost:4200/articles/:id

Test Realtime Capabilities

Open up the Firebase Console and navigate to Firestore. Add another document with a title, description, and img. Then quickly switch back to your deployed app. You should see the new article pop-up in seconds without refreshing the page.

Test Realtime Capabilities

Deploy live to the world

Now that we’ve tested it locally. Lets deploy it up to Firebase Hosting so we can share it with our friends!

Copy FirebaseConfig into environment.prod.ts file

This will be the same as src/environments/environment.ts


    export const environment = {
     production: false,
     firebaseConfig: {
       apiKey: "Axxxxxx_xxxxxxxxxxxxx",
       authDomain: "myapp-xxxxxx.firebaseapp.com",
       databaseURL: "https://myapp-xxxxxxx.firebaseio.com",
       projectId: "myapp-xxxxxxx",
       storageBucket: "myapp-xxxxxxx.appspot.com",
       messagingSenderId: "xxxxxxxxxx"
     }
    };
    Build Angular App
    $myapp: ng build --prod

Deploy to Firebase Hosting

If you don’t have the Firebase CLI setup or are missing certain Firebase Hosting configuration please see Part One: Angular with Firebase Hosting.


    $myapp: firebase deploy

Open your host URL

Congratulations! You just created an Angular application with realtime updates from Firestore and then you re-deployed that application as an HTTPS website using Firebase Hosting. In the next article we are going to cover Create, Update, and Delete using AngularFire. See you there!

Review

  • Overview of Firestore and the AngularFire library
  • Creating collection and documents
  • Overview of Firestore Data Model
  • Basic queries with AngularFire
  • Angular + TypeScript key concepts in the code
  • Deploying to Firebase Hosting

What’s next?

Stay tuned for weekly releases of the Angular Firebase series. Where we will be covering Firebase Hosting, Firestore, Cloud Functions, Cloud Storage, and Authentication!

Good luck with your journey into the world of Serverless!

Thanks for reading :)

About Mode2

Mode2.com (www.mode2.com) is a cloud consulting and services partner that helps US-based companies to extract the maximum value from cloud native computing and digital transformation. Our mission is to provide expert advice and cloud services that produce outstanding results for our clients in their cloud adoption journey. Mode2 is an Advanced Consulting Partner for Amazon Web Services, and a Google Cloud Consulting Partner. Our HQ is in Los Angeles, CA.