Angular 6 Firebase Tutorial: Firestore CRUD Web Application

by Didin J., updated on Aug 19, 2019 Angular 6 Firebase Tutorial: Firestore CRUD Web Application

The Comprehensive step by step tutorial on build Firestore CRUD (Create, Read, Update, Delete) Web Application using Angular 6 and Firebase

The Comprehensive step by step tutorial on build Firestore CRUD (Create, Read, Update, Delete) Web Application using Angular 6 and Firebase. This tutorial was similar to previous Ionic 4 and Angular 6 tutorial using Firebase Real-time Database. Now, we show you how to use Angular 6 with Firestore Database using Firebase module/library.


Shortcut to the steps:


This tutorial compatible with Angular 6, Angular 7, and Angular 8 by matching the version of the Firebase module.

The following tools, framework, and module are required for this tutorial:

We assume that you have downloaded and installed Node.js environment. Now, let's check the above requirement by open the terminal or Node command line then go to your projects folder. Type this command to check the latest Node and NPM version.

node -v
v8.12.0
npm -v
6.4.1

That's our Node and NPM versions, let's move to the main steps of the Angular 6 and Firebase Firestore tutorial.


Setup Firebase Firestore Database

Setup Google Firebase is very simple. Open your browser then go to Google Firebase Console and log in using your Google account.

Angular 6 Firebase Tutorial: Firestore CRUD Web Application - Firebase Console

Click the `Add Project` button, name it as you like (ours: Djamware) and checks all checkboxes. Finally, click `Create Project` button then wait a few seconds and click the `Continue` button. You will be redirected to this page.

Angular 6 Firebase Tutorial: Firestore CRUD Web Application - Firebase Project

Go to Develop menu on the left menu then click `Create Database` on Cloud Firestore page.

Angular 6 Firebase Tutorial: Firestore CRUD Web Application - Security Rules

Choose `Start in test mode` then click `Enabled` button.

Angular 6 Firebase Tutorial: Firestore CRUD Web Application - Firestore Database

Click `Add Collection` button to add the new collection for this tutorial. Fill collection ID (ours: 'boards') then click next. Add the required fields then click finish the wizard.

Angular 6 Firebase Tutorial: Firestore CRUD Web Application - Firestore Collection

Now, the Firebase Firestore Database is ready to access. Make sure that your iOS and Android app match with the configuration files (JSON/PList).


Install Angular-CLI and Create Angular 6 Application

To install or upgrade the latest Angular 6 CLI, type this command in the terminal or Node command line.

sudo npm install -g @angular/cli

If you use windows, it might be not necessary to add `sudo`. Next, create a new Angular 6 Web Application using this Angular CLI command.

ng new angular6-firestore

That command will create a new Angular 6 application with the name `angular6-firestore`. Next, go to the newly created Angular 6 project folder.

cd angular6-firestore

Now, you run the new Angular 6 web application using your own host and default port.

ng serve --host 0.0.0.0

Open your browser then go to `localhost:4200` you will see this Angular 6 page.

Angular 6 Firebase Tutorial: Firestore CRUD Web Application - Angular 6 Welcome Page


Install and Configure the Firebase Module

To access the Firebase Firestore Database, we just need to install the Firebase module. Type this command to install it.

npm install --save firebase

Next, register the Firebase module in the Angular 6 app by open and edit this file `src/app/app.component.ts` then add these imports of Firebase and Firestore.

import * as firebase from 'firebase';
import firestore from 'firebase/firestore';

Declare a constant variable for holds Firebase and Firestore setting before `@Component` which you need to fill the Firebase apiKey, authDomain, databaseURL, projectId, and storageBucket.

const settings = {timestampsInSnapshots: true};
const config = {
  apiKey: 'YOUR_APIKEY',
  authDomain: 'YOUR_AUTH_DOMAIN',
  databaseURL: 'YOUR_DATABASE_URL',
  projectId: 'YOUR_PROJECT_ID',
  storageBucket: 'YOUR_STORAGE_BUCKET',
};

Add `ngOnInit` Angular 6 builtin function while calling the Firebase and Firestore config.

ngOnInit() {
  firebase.initializeApp(config);
  firebase.firestore().settings(settings);
}

Now, the Firebase is ready to use within the Angular 6 Application.


Create Angular 6 Routes

To create Angular 6 Routes for navigation between Angular 6 pages/component, add or generate all required component.

ng g component boards
ng g component boards-detail
ng g component boards-create
ng g component boards-edit

Next, open and edit again `src/app/app.module.ts` then add this import.

import { RouterModule, Routes } from '@angular/router';

Add these lines of codes for the required Angular Components routes before `@NgModule`.

const appRoutes: Routes = [
  {
    path: 'boards',
    component: BoardsComponent,
    data: { title: 'Boards List' }
  },
  {
    path: 'boards-details/:id',
    component: BoardsDetailComponent,
    data: { title: 'Boards Details' }
  },
  {
    path: 'boards-create',
    component: BoardsCreateComponent,
    data: { title: 'Create Boards' }
  },
  {
    path: 'boards-edit/:id',
    component: BoardsEditComponent,
    data: { title: 'Edit Boards' }
  },
  { path: '',
    redirectTo: '/boards',
    pathMatch: 'full'
  }
];

In @NgModule imports, section adds ROUTES constant, so the imports section will be like this.

imports: [
  RouterModule.forRoot(appRoutes),
  BrowserModule
],

To activate that routes in Angular 6, open and edit `src/app/app.component.html` then replace all codes with this.

<div style="text-align:center">
  <h1>
    Angular 6 Firebase
  </h1>
  <img width="150" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<router-outlet></router-outlet>


Install and Configure Angular 6 Material

For user interface (UI) we will use Angular 6 Material. There's a new feature for generating a Material component like Table as a component, but we will create or add the Table component from scratch to existing component. Type this command to install Angular Material.

ng add @angular/material

We will register all required Angular Material components or modules to `app.module.ts`. Open and edit that file then add these imports of the required Angular Material Components or Modules.

import {
  MatInputModule,
  MatPaginatorModule,
  MatProgressSpinnerModule,
  MatSortModule,
  MatTableModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule } from "@angular/material";

Also, modify `FormsModule` import to add `ReactiveFormsModule`.

import { FormsModule, ReactiveFormsModule } from '@angular/forms';

Register the above modules to `@NgModule` imports.

imports: [
  RouterModule.forRoot(appRoutes),
  BrowserModule,
  BrowserAnimationsModule,
  FormsModule,
  ReactiveFormsModule,
  BrowserAnimationsModule,
  MatInputModule,
  MatTableModule,
  MatPaginatorModule,
  MatSortModule,
  MatProgressSpinnerModule,
  MatIconModule,
  MatButtonModule,
  MatCardModule,
  MatFormFieldModule
],


Create Angular 6 Service for CRUD Operation

The CRUD (Create, Read, Update, Delete) operation to Firebase Firestore Database is through Angular 6 Service. For that, generate Angular 6 Service by type this command.

ng g service Fs

Open and edit `src/app/fs.service.ts` then add these imports of RxJS, Observable, map, Firebase, and Firestore.

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import * as firebase from 'firebase';
import firestore from 'firebase/firestore'

Declare a variable for accessing Firestore collection before the constructor.

ref = firebase.firestore().collection('boards');

Add all of these CRUD functions.

getBoards(): Observable<any> {
  return new Observable((observer) => {
    this.ref.onSnapshot((querySnapshot) => {
      let boards = [];
      querySnapshot.forEach((doc) => {
        let data = doc.data();
        boards.push({
          key: doc.id,
          title: data.title,
          description: data.description,
          author: data.author
        });
      });
      observer.next(boards);
    });
  });
}

getBoard(id: string): Observable<any> {
  return new Observable((observer) => {
    this.ref.doc(id).get().then((doc) => {
      let data = doc.data();
      observer.next({
        key: doc.id,
        title: data.title,
        description: data.description,
        author: data.author
      });
    });
  });
}

postBoards(data): Observable<any> {
  return new Observable((observer) => {
    this.ref.add(data).then((doc) => {
      observer.next({
        key: doc.id,
      });
    });
  });
}

updateBoards(id: string, data): Observable<any> {
  return new Observable((observer) => {
    this.ref.doc(id).set(data).then(() => {
      observer.next();
    });
  });
}

deleteBoards(id: string): Observable<{}> {
  return new Observable((observer) => {
    this.ref.doc(id).delete().then(() => {
      observer.next();
    });
  });
}


Create List of Boards

To display a list of boards, open and edit `src/app/boards/boards.component.ts` then add these imports.

import { DataSource } from '@angular/cdk/collections';
import { FsService } from '../fs.service';

Declare these variables and inject `FsService` to the constructor.

displayedColumns = ['title', 'description', 'author'];
dataSource = new BoardDataSource(this.fs);

constructor(private fs: FsService) {
}

Create a new class below the existing class.

export class BoardDataSource extends DataSource<any> {

  constructor(private fs: FsService) {
    super()
  }

  connect() {
    return this.fs.getBoards();
  }

  disconnect() {

  }
}

Next, open and edit `src/app/boards/boards.component.html` then replace all HTML tags with these Angular Material tables, cells, columns tags.

<div class="button-row">
  <a mat-fab color="primary" [routerLink]="['/boards-create']"><mat-icon>add</mat-icon></a>
</div>
<div class="example-container mat-elevation-z8">
  <table mat-table #table [dataSource]="dataSource">

    <!--- Note that these columns can be defined in any order.
          The actual rendered columns are set as a property on the row definition" -->

    <!-- Title Column -->
    <ng-container matColumnDef="title">
      <th mat-header-cell *matHeaderCellDef> Title </th>
      <td mat-cell *matCellDef="let element" class="title-col"> {{element.title}} </td>
    </ng-container>

    <!-- Description Column -->
    <ng-container matColumnDef="description">
      <th mat-header-cell *matHeaderCellDef> Description </th>
      <td mat-cell *matCellDef="let element"> {{element.description}} </td>
    </ng-container>

    <!-- Author Column -->
    <ng-container matColumnDef="author">
      <th mat-header-cell *matHeaderCellDef> Author </th>
      <td mat-cell *matCellDef="let element"> {{element.author}} </td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="['/boards-details/', row.key]"></tr>
  </table>
</div>

Finally, give the styles to for this page by open and edit `src/app/boards/boards.components.css` then replace all CSS codes with this.

.example-container {
  display: flex;
  flex-direction: column;
  max-height: 500px;
  min-width: 300px;
  overflow: auto;
}

.title-col {
  flex: 0 0 100px !important;
  white-space: unset !important;
}

.button-row {
  margin: 10px 0;
}


Show Details and Delete Board

To show boards details after click or tap on the one of a row inside the Angular Material table, open and edit `src/boards-detail/boards-detail.component.ts` then add these imports of ActivatedRoute, Router (@angular/router), FsService (Firebase Service).

import { ActivatedRoute, Router } from '@angular/router';
import { FsService } from '../fs.service';

Inject above modules to the constructor.

constructor(private route: ActivatedRoute, private router: Router, private fs: FsService) { }

Declare a variable before the constructor for hold board data that get from the API.

board = {};

Add a function for getting a single Board data from the Firebase Service.

getBoardDetails(id) {
  this.fs.getBoard(id)
    .subscribe(data => {
      console.log(data);
      this.board = data;
    });
}

Call that function when the component is initiated.

ngOnInit() {
  this.getBoardDetails(this.route.snapshot.params['id']);
}

Next, add the function for delete board by ID.

deleteBoard(id) {
  this.fs.deleteBoards(id)
    .subscribe(res => {
        this.router.navigate(['/boards']);
      }, (err) => {
        console.log(err);
      }
    );
}

For the view, open and edit `src/board-detail/board-detail.component.html` then replace all HTML tags with this Angular Material Fab, Card, and it's contents.

<div class="button-row">
  <a mat-fab color="primary" [routerLink]="['/boards']"><mat-icon>list</mat-icon></a>
</div>
<mat-card class="example-card">
  <mat-card-header>
    <mat-card-title><h2>{{board.title}}</h2></mat-card-title>
    <mat-card-subtitle>{{board.description}}</mat-card-subtitle>
  </mat-card-header>
  <mat-card-content>
    <dl>
      <dt>ISBN:</dt>
      <dd>{{board.isbn}}</dd>
      <dt>Author:</dt>
      <dd>{{board.author}}</dd>
      <dt>Publisher:</dt>
      <dd>{{board.publisher}}</dd>
      <dt>Publish Year:</dt>
      <dd>{{board.published_year}}</dd>
      <dt>Update Date:</dt>
      <dd>{{board.updated_date | date}}</dd>
    </dl>
  </mat-card-content>
  <mat-card-actions>
    <a mat-fab color="primary" [routerLink]="['/board-edit', board.key]"><mat-icon>edit</mat-icon></a>
    <a mat-fab color="warn" (click)="deleteBoard(board.key)"><mat-icon>delete</mat-icon></a>
  </mat-card-actions>
</mat-card>

Finally, open and edit `src/boards-detail/boards-detail.component.css` then add these lines of CSS codes.

.example-card {
  max-width: 500px;
}

.button-row {
  margin: 10px 0;
}


Create Add Board with Angular FormGroup

To create a form for adding a Board, open and edit `src/boards-create/boards-create.component.ts` then add these imports of Router, Firebase Service, FormControl, FormGroupDirective, FormGroup, FormBuilder, NgForm, and Validators.

import { Router } from '@angular/router';
import { FsService } from '../fs.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';

Inject above modules to the constructor.

constructor(private router: Router, private fs: FsService, private formBuilder: FormBuilder) { }

Declare variables for the Form Group and all of the required fields inside the form before the constructor.

boardsForm: FormGroup;
title:string='';
description:string='';
author:string='';

Add initial validation for each field.

ngOnInit() {
  this.boardsForm = this.formBuilder.group({
    'title' : [null, Validators.required],
    'description' : [null, Validators.required],
    'author' : [null, Validators.required]
  });
}

Create a function for submitting or POST board form.

onFormSubmit(form:NgForm) {
  this.fs.postBoards(form)
    .subscribe(res => {
        let id = res['key'];
        this.router.navigate(['/boards-details', id]);
      }, (err) => {
        console.log(err);
      });
}

Next, open and edit `src/boards-create/boards-create.component.html` then replace all HTML tags with this Angular Material Fab, FormGroup, Input, Text Area, Form Field, etc.

<div class="button-row">
  <a mat-fab color="primary" [routerLink]="['/boards']"><mat-icon>list</mat-icon></a>
</div>
<form [formGroup]="boardsForm" (ngSubmit)="onFormSubmit(boardsForm.value)">
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Title" formControlName="title"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!boardsForm.get('title').valid && boardsForm.get('title').touched">Please enter Board Title</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <textarea matInput placeholder="Description" formControlName="description"
           [errorStateMatcher]="matcher"></textarea>
    <mat-error>
      <span *ngIf="!boardsForm.get('description').valid && boardsForm.get('description').touched">Please enter Board Description</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Author" formControlName="author"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!boardsForm.get('author').valid && boardsForm.get('author').touched">Please enter Board Author</span>
    </mat-error>
  </mat-form-field>
  <div class="button-row">
    <button type="submit" [disabled]="!boardsForm.valid" mat-fab color="primary"><mat-icon>save</mat-icon></button>
  </div>
</form>

Finally, open and edit `src/boards-create/boards-create.component.css` then add these CSS codes.

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}


Create Edit Board with Angular FormGroup

We have put an edit button inside the Board Detail component. Now, open and edit `src/boards-edit/boards-edit.component.ts` then add these imports of Router, ActivatedRoute, Firebase Service, FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, and Validators.

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FsService } from '../fs.service';
import { FormControl, FormGroupDirective, FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';

Inject above modules to the constructor.

constructor(private router: Router, private route: ActivatedRoute, private fs: FsService, private formBuilder: FormBuilder) { }

Declare the Form Group variable and all of the required variables for the board form before the constructor.

boardsForm: FormGroup;
id:string = '';
title:string = '';
description:string = '';
author:string = '';

Next, add validation for all fields when the component is initiated.

ngOnInit() {
  this.getBoard(this.route.snapshot.params['id']);
  this.boardsForm = this.formBuilder.group({
    'title' : [null, Validators.required],
    'description' : [null, Validators.required],
    'author' : [null, Validators.required]
  });
}

Create a function for getting boards data that filled to each form fields.

getBoard(id) {
  this.fs.getBoard(id).subscribe(data => {
    this.id = data.key;
    this.boardsForm.setValue({
      title: data.title,
      description: data.description,
      author: data.author
    });
  });
}

Create a function to update the board changes.

onFormSubmit(form:NgForm) {
  this.fs.updateBoards(this.id, form)
    .subscribe(res => {
        this.router.navigate(['/boards']);
      }, (err) => {
        console.log(err);
      }
    );
}

Add a function for handling the show board details button.

boardsDetails() {
  this.router.navigate(['/boards-details', this.id]);
}

Next, open and edit `src/boards-edit/boards-edit.component.html` then replace all HTML tags with this.

<div class="button-row">
  <a mat-fab color="primary" (click)="boardsDetails()"><mat-icon>show</mat-icon></a>
</div>
<form [formGroup]="boardsForm" (ngSubmit)="onFormSubmit(boardsForm.value)">
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Title" formControlName="title"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!boardsForm.get('title').valid && boardsForm.get('title').touched">Please enter Book Title</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <textarea matInput placeholder="Description" formControlName="description"
           [errorStateMatcher]="matcher"></textarea>
    <mat-error>
      <span *ngIf="!boardsForm.get('description').valid && boardsForm.get('description').touched">Please enter Book Description</span>
    </mat-error>
  </mat-form-field>
  <mat-form-field class="example-full-width">
    <input matInput placeholder="Author" formControlName="author"
           [errorStateMatcher]="matcher">
    <mat-error>
      <span *ngIf="!boardsForm.get('author').valid && boardsForm.get('author').touched">Please enter Book Author</span>
    </mat-error>
  </mat-form-field>
  <div class="button-row">
    <button type="submit" [disabled]="!boardsForm.valid" mat-fab color="primary"><mat-icon>save</mat-icon></button>
  </div>
</form>

Finally, open and edit `src/boards-edit/boards-edit.component.css` then add these lines of CSS codes.

.example-form {
  min-width: 150px;
  max-width: 500px;
  width: 100%;
}

.example-full-width {
  width: 100%;
}

.example-full-width:nth-last-child() {
  margin-bottom: 10px;
}

.button-row {
  margin: 10px 0;
}


Run and Test The Angular 6 Firebase Firestore Application

Now, it's time for testing the Angular 6 Firebase Firestore CRUD Web Application. Type this command again to run the application.

ng serve --host 0.0.0.0

In the browser go to this URL `localhost:4200` and here the whole application looks like.

Angular 6 Firebase Tutorial: Firestore CRUD Web Application - Board List
Angular 6 Firebase Tutorial: Firestore CRUD Web Application - Board Details
Angular 6 Firebase Tutorial: Firestore CRUD Web Application - Edit Board
Angular 6 Firebase Tutorial: Firestore CRUD Web Application - Create Board

That it's, the Angular 6 Firebase Tutorial: Firestore CRUD Web Application. You can find the full working source code from our GitHub.

If you don’t want to waste your time design your own front-end or your budget to spend by hiring a web designer then Angular Templates is the best place to go. So, speed up your front-end web development with premium Angular templates. Choose your template for your front-end project here.

That just the basic. If you need more deep learning about MEAN Stack, Angular, and Node.js, you can take the following cheap course:

Thanks!