Part Two: Angular with Firestore — Intro
Read more below.
Read more below.
10 min read · Ryan Jones
Thank you and enjoy! Github code, here.
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.
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.
AngularFire is an npm package which makes it easier to work with Firebase products when building Angular applications.
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).
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.
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"
}
]
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.
{
"title": "Are Colleges worth the debt?",
"description": "A comprehensive breakdown of the College debt crisis."
"id": "456DEF"
}
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.
Below, we are going to mock out some data for our Angular application. Navigate to the Firebase Console.
Create first document
Create second document
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.
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.
You can find the official installation documentation for AngularFire2, on their GitHub.
$myapp: npm install firebase angularfire2 --save
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"
}
};
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
]
})
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.
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
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.
We need to add HTML and TypeScript for our component to work properly with Firestore.
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.
<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.
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));
}
}
We need to add HTML and TypeScript for our component to work properly with Firestore.
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>
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.
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
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/:id
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.
Now that we’ve tested it locally. Lets deploy it up to Firebase Hosting so we can share it with our friends!
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
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
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!
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 :)
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.