Create Angular 7 Firebase 6 CRUD Web App with Angular Material 7

By Digamber Rawat Last updated on
In this Angular tutorial, we are going to build a simple Angular 7 CRUD (Create, Read, Update and Delete) web app with Angular Material 7 and Firebase real-time NoSQL database (version 6.0.2). We’ll take the help of Angular Material 7 for creating beautiful UI components for our demo app.

We’ll be storing data on cloud with the help of Firebase NoSQL database. We’ll learn how to connect Firebase with Angular 7 web app.

For the demo purpose, I will be creating a book store inventory CRUD web application. In this Book management app user will be able to do the following things:

Add, Read, Edit and Delete book from Firebase db using AngularFire 2 library

  • Add book name
  • Add author name
  • Add 10 digit Book ISBN
  • Add bookbinding type using Angular Material dropdown
  • Include book publication date
  • Add multiple languages
  • Manage stock using Angular material radio buttons

01. Angular 7 Firebase Tutorial: Prerequisite

Setup Node JS
Before we move further I assume you are already having Node JS set up in your machine. If not theN follow the given below link:

Follow this link How to Set up Node JS Development Environment?

Install Latest Angular CLI
Use the below command to install the Angular CLI. Avoid, if you’ve already installed the Angular CLI.

npm install -g @angular/cli

02. Angular 7 Firebase Tutorial: Project Set Up

Then, set up a new Angular 7 project for creating CRUD app using given below command in your terminal.

angular-material7-firebase-crud-app

Angular CLI will ask you a few questions, select ‘Yes’ and ‘CSS’ (You may also select whatever you’d like to go with).

? Would you like to add Angular routing? (y/N) = yes
? Which stylesheet format would you like to use? = CSS

Your Angular 7 project is installed, it’s time to enter into the project directory.

cd angular-material-firebase-crud-app

03. Angular 7 Firebase Tutorial: Create and Setup Angular 7 Routes

Angular 7 had already included the routes when we were creating the application in the earlier step. Before we enable the routes we need to create components in the Angular app so that we can use them while creating the routes.

ng g component components/add-book --module app
ng g component components/edit-book --module app
ng g component components/book-list --module app

We are using --module app parameter with Angular CLI command because we have 2 module files in the app folder. Now with the –module app parameter We are narrating Angular CLI that app.module.ts is our main app module file.

Go to app > app-routing.module.ts file and add the below code.

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

import { AddBookComponent } from './components/add-book/add-book.component';
import { BookListComponent } from './components/book-list/book-list.component';
import { EditBookComponent } from './components/edit-book/edit-book.component';

const routes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'add-book' },
  { path: 'add-book', component: AddBookComponent },
  { path: 'edit-book/:id', component: EditBookComponent },
  { path: 'books-list', component: BookListComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

export class AppRoutingModule { }

04. Angular 7 Firebase Tutorial: Add Angular Material 7 UI Library in Angular 7 Project

In next step we are going to set up Angular material UI library in our Angular 7 CRUD web app. This UI library follows Google’s Material design guidelines to create user interfaces.

Let us build a basic book inventory CRUD web application with Angular Material. While implementing the Angular Material, we’ll be taking help of Angular Material official documentation.

Run the command to install Angualr material.

ng add @angular/material

Angualr CLI will ask as to choose the Angular Material theme, i will select `Indigo/Pink`. However you may choose whatever material theme you like.

? Choose a prebuilt theme name, or "custom" for a custom theme: Indigo/Pink

❯ Indigo/Pink        [ Preview: https://material.angular.io?theme=indigo-pink ] 
  Deep Purple/Amber  [ Preview: https://material.angular.io?theme=deeppurple-amber ] 
  Pink/Blue Grey     [ Preview: https://material.angular.io?theme=pink-bluegrey ] 
  Purple/Green       [ Preview: https://material.angular.io?theme=purple-green ]

Then it will ask for Hammer.js(gesture recognition) and Browser animation support . Select yes and hit enter.

# Set up HammerJS for gesture recognition? (Y/n) = Y
# ? Set up browser animations for Angular Material? (Y/n) = Y

We’ll be taking help of Angular material 7 icons and Roboto font in our Angular 7 CRUD web app.

Go to src > index.html file and paste the following lines of code in the header section like given below.

<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

Then go to src > styles.css file and import the Angular material 7 theme and CRUD web project css like given below.

@import "~@angular/material/prebuilt-themes/indigo-pink.css";html,body{height:100%;}

body{margin:0;font-family:'Roboto', sans-serif;}
.header{justify-content:space-between;}
.user-profile{margin-left:15px;}
.mat-sidenav-container{height:100%;display:flex;flex:1 1 auto;}
.mat-nav-list .mat-list-item{font-size:15px;}
.nav-tool-items{display:inline-block;margin-right:13px;}
.user-profile{margin-left:15px;cursor:pointer;}
.hamburger{visibility:hidden !important;}
.mat-sidenav,.mat-sidenav-content{padding:15px;}
.mat-list-item.active{background:rgba(0, 0, 0, .04);}
.mat-sidenav-content{padding:25px 40px 0;}
.mat-sidenav{background-color:#F2F2F2;width:250px;}
.header{position:sticky;position:-webkit-sticky;top:0;z-index:1000;}
mat-sidenav mat-icon{margin-right:12px;}
.hamburger{margin-top:5px;cursor:pointer;}
.mat-radio-button,.mat-radio-group{margin-right:25px;}
.controlers-wrapper>*{width:100%;padding:0;}
.misc-bottom-padding{margin:8px 0 10px;}
.misc-bottom-padding mat-label{margin-right:15px;}
mat-radio-group mat-radio-button{margin-left:5px;}
.button-wrapper button{margin-right:5px;}
table.mat-table,table{width:100%;}
.inner-wrapper{padding:15px 0 130px;width:100%;}
.inner-wrapper mat-card{display:inline-block;margin:0 6% 0 0;vertical-align:top;width:44%;}
.full-wrapper{width:100%;}
.multiple-items{position:relative;}
.multiple-items .tooltip-info{right:0;top:7px;cursor:pointer;color:#a1a7c7;position:absolute;font-size:20px;}
body .push-right{margin-right:10px;}
.no-data{text-align:center;padding-top:30px;color:#6c75a9;}
.button-wrapper{margin:20px 0 0 0;}
@media (max-width:1024px){.inner-wrapper mat-card{width:100%;}
 .mat-sidenav-content{padding:20px 20px 0;}
 .misc-bottom-padding mat-label{display:block;padding-bottom:10px;}
 .mat-sidenav{width:230px;}
 .mat-nav-list .mat-list-item{font-size:14px;}
}
@media (max-width:767px){.nav-tool-items{margin-right:0;}
 .hamburger{visibility:visible !important;}
}
html, body{height:100%;}
body{margin:0;font-family:Roboto, "Helvetica Neue", sans-serif;}

Now you are ready to use Angular Material UI components in your Angular 7 Firebase 6 CRUD web app.

Create a Custom Angular Material Module

Create app > material.module.ts file and add the following code into it.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import {
   MatButtonModule,
   MatToolbarModule,
   MatIconModule,
   MatBadgeModule,
   MatSidenavModule,
   MatListModule,
   MatGridListModule,
   MatFormFieldModule,
   MatInputModule,
   MatSelectModule,
   MatRadioModule,
   MatDatepickerModule,
   MatNativeDateModule,
   MatChipsModule,
   MatTooltipModule,
   MatTableModule,
   MatPaginatorModule
} from '@angular/material';

@NgModule({
   imports: [
      CommonModule,
      MatButtonModule,
      MatToolbarModule,
      MatIconModule,
      MatSidenavModule,
      MatBadgeModule,
      MatListModule,
      MatGridListModule,
      MatFormFieldModule,
      MatInputModule,
      MatSelectModule,
      MatRadioModule,
      MatDatepickerModule,
      MatNativeDateModule,
      MatChipsModule,
      MatTooltipModule,
      MatTableModule,
      MatPaginatorModule
   ],
   exports: [
      MatButtonModule,
      MatToolbarModule,
      MatIconModule,
      MatSidenavModule,
      MatBadgeModule,
      MatListModule,
      MatGridListModule,
      MatInputModule,
      MatFormFieldModule,
      MatSelectModule,
      MatRadioModule,
      MatDatepickerModule,
      MatChipsModule,
      MatTooltipModule,
      MatTableModule,
      MatPaginatorModule
   ],
   providers: [
      MatDatepickerModule,
   ]
})

export class AngularMaterialModule { }

Then go to app.module.ts file and import AngularMaterialModule.

/* Angular material */
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AngularMaterialModule } from './material.module';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@NgModule({
  declarations: [...],
  imports: [
    BrowserAnimationsModule,
    AngularMaterialModule,
  ],
  providers: [...],
  bootstrap: [...],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})

export class AppModule { }

Now you can import any Angular Material UI component in your custom Angular material module.

Create Angular Material 7 Basic Responsive Layout

Now we have to create a basic Angular app with Angular Material. I’ll take the help of Angular Material’s APIs to create basic responsive layout.

Go to app.component.html file and add the below code. It contains the basic layout created with Angular Material 7 ui library.

<!-- Toolbar -->
<mat-toolbar color="primary" class="header">
  <div>Book Store</div>
  <span class="nav-tool-items">
    <mat-icon (click)="sidenav.toggle()" class="hamburger">menu</mat-icon>
  </span>
</mat-toolbar>

<mat-sidenav-container>
  <!-- Sidenav -->
  <mat-sidenav #sidenav [mode]="isBiggerScreen() ? 'over' : 'side'" [(opened)]="opened" [fixedInViewport]="true"
    [fixedTopGap]>
    <mat-nav-list>
      <a mat-list-item routerLinkActive="active" routerLink="/add-book">
        <mat-icon>add</mat-icon> Add Book
      </a>
      <a mat-list-item routerLinkActive="active" routerLink="/books-list">
        <mat-icon>format_list_bulleted</mat-icon> View Books
      </a>
    </mat-nav-list>
  </mat-sidenav>

  <!-- Main content -->
  <mat-sidenav-content>
    <router-outlet></router-outlet>
  </mat-sidenav-content>
</mat-sidenav-container>

After that, go to app.component.ts file and paste the following code.

import { Component, ViewChild, HostListener, OnInit } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {
  opened = true;
  @ViewChild('sidenav') sidenav: MatSidenav;

  ngOnInit() {
    if (window.innerWidth < 768) {
      this.sidenav.fixedTopGap = 55;
      this.opened = false;
    } else {
      this.sidenav.fixedTopGap = 55;
      this.opened = true;
    }
  }

  @HostListener('window:resize', ['$event'])
  onResize(event) {
    if (event.target.innerWidth < 768) {
      this.sidenav.fixedTopGap = 55;
      this.opened = false;
    } else {
      this.sidenav.fixedTopGap = 55
      this.opened = true;
    }
  }

  isBiggerScreen() {
    const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
    if (width < 768) {
      return true;
    } else {
      return false;
    }
  }
}

05. Angular 7 Firebase Tutorial: Set up AngularFire2 Library in Angular 7 App

In order to use Firebase 6 real-time NoSQL database in your Angular 7 project, you need to install AngularFire library from NPM.

I assume you already have account set up in Firebase database, if not then follow this tutorial: Setup Account in Firebase Database and Connect with Angular App.

Enter the below command in the terminal.

npm install firebase @angular/fire --save

To make connection between your Firebase 6 real-time NoSQL database and Angular 7 app. Go to src > environments folder and add your Firebase config details in both the environment files.

environment.prod.ts

export const environment = {
  production: true,
  firebase: {
    apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    authDomain: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    databaseURL: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    projectId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    storageBucket: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    messagingSenderId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  }
};

environment.ts

export const environment = {
  production: false,
  firebase: {
    apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    authDomain: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    databaseURL: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    projectId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    storageBucket: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    messagingSenderId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  }
};

Import AngularFirebase 2 services in app.module.ts file.

/* Firebase */
import { AngularFireModule } from '@angular/fire';
import { AngularFireDatabaseModule } from '@angular/fire/database';
import { environment } from 'src/environments/environment';

@NgModule({
  declarations: [...],
  imports: [
    AngularFireModule.initializeApp(environment.firebaseConfig),
    AngularFireDatabaseModule
  ],
  providers: [...],
  bootstrap: [...]
})

export class AppModule { }

06. Angular 7 Firebase Tutorial: Create Service to Manage CRUD operations via RESTful APIs using AngularFire2 Library

Now create a folder by the name of shared, in this folder we’ll keep our service file and book interface class. This service & interface file will help us to manage CRUD operations for our Angular Material and Angular 7 Firebase web app.

Run the command…

Enter the below command to create the book interface class for setting up data types for a book inventory app.

ng g i shared/book

app > shared > book.ts

export interface Book {
   $key: string;
   book_name: string;
   isbn_10: number;
   author_name: string
   publication_date: Date;
   binding_type: string;
   in_stock: string;
   languages: Array<string>;
}

Its time to create book service, run the following command.

ng g s shared/book

Our book.service.ts file holds the core logic for our Angular material web app. We are using AngularFire2 library to build create, read, update & delete operations.

import { Injectable } from '@angular/core';
import { Book } from './book';
import { AngularFireDatabase, AngularFireList, AngularFireObject } from '@angular/fire/database';

@Injectable({
  providedIn: 'root'
})

export class BookService {
  booksRef: AngularFireList<any>;
  bookRef: AngularFireObject<any>;

  constructor(private db: AngularFireDatabase) {}

  /* Create book */
  AddBook(book: Book) {
    this.booksRef.push({
      book_name: book.book_name,
      isbn_10: book.isbn_10,
      author_name: book.author_name,
      publication_date: book.publication_date,
      binding_type: book.binding_type,
      in_stock: book.in_stock,
      languages: book.languages
    })
    .catch(error => {
      this.errorMgmt(error);
    })
  }

  /* Get book */
  GetBook(id: string) {
    this.bookRef = this.db.object('books-list/' + id);
    return this.bookRef;
  }  

  /* Get book list */
  GetBookList() {
    this.booksRef = this.db.list('books-list');
    return this.booksRef;
  }

  /* Update book */
  UpdateBook(id, book: Book) {
    this.bookRef.update({
      book_name: book.book_name,
      isbn_10: book.isbn_10,
      author_name: book.author_name,
      publication_date: book.publication_date,
      binding_type: book.binding_type,
      in_stock: book.in_stock,
      languages: book.languages
    })
    .catch(error => {
      this.errorMgmt(error);
    })
  }

  /* Delete book */
  DeleteBook(id: string) {
    this.bookRef = this.db.object('books-list/' + id);
    this.bookRef.remove()
    .catch(error => {
      this.errorMgmt(error);
    })
  }

  // Error management
  private errorMgmt(error) {
    console.log(error)
  }
}

Go to app.module.ts file and import the Angular service and also import into the providers array like given below.

/* Angular CRUD services */
import { BookService } from './shared/book.service';

@NgModule({
  providers: [BookService],
})

export class AppModule { }

07. Angular 7 Firebase Tutorial: Add Book using Angular Material 7 and AngularFire2

In this part of the tutorial, we are going to cover following topics using given below logic.

  • Creating Reactive Forms with Angualr Material 7.
  • Validating Reactive Forms with Angular Material form elements.
  • Creating Radio buttons with Angular Material 7.
  • Creating Reactive Nested Form with Angular 7 – Firebase 6 and Angular Material 7.
  • Saving data in Firebase Real-time database using AngularFire2 RESTful APIs.
  • Save and store date to Firebase using Angular Material date-picker element.
  • Create custom form reset functionality to reset Reactive forms FormGroup.
  • Working with Angular Material 7 Chips Input with Reactive Forms.
  • Angular Material Datepicker in Angular 7 and save in Firebase.

We need to import FormsModule and ReactiveFormsModule in app.module.ts file.

/* Reactive form services in Angular 7 */
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

@NgModule({
  imports: [
    FormsModule,
    ReactiveFormsModule
  ],
})

export class AppModule { }

To add a book into the inventory, go to components > add-book.component.ts file and include the following imports.

import { Component, OnInit, ViewChild } from '@angular/core';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material';
import { BookService } from './../../shared/book.service';
import { FormGroup, FormBuilder, Validators } from "@angular/forms";

export interface Language {
  name: string;
}

@Component({
  selector: 'app-add-book',
  templateUrl: './add-book.component.html',
  styleUrls: ['./add-book.component.css']
})
export class AddBookComponent implements OnInit {
  visible = true;
  selectable = true;
  removable = true;
  addOnBlur = true;
  languageArray: Language[] = [];
  @ViewChild('chipList') chipList;
  @ViewChild('resetBookForm') myNgForm;
  readonly separatorKeysCodes: number[] = [ENTER, COMMA];
  selectedBindingType: string;
  bookForm: FormGroup;
  BindingType: any = ['Paperback', 'Case binding', 'Perfect binding', 'Saddle stitch binding', 'Spiral binding'];

  ngOnInit() { 
    this.bookApi.GetBookList();
    this.submitBookForm();
  }

  constructor(
    public fb: FormBuilder,
    private bookApi: BookService
  ) { }

  /* Remove dynamic languages */
  remove(language: Language): void {
    const index = this.languageArray.indexOf(language);
    if (index >= 0) {
      this.languageArray.splice(index, 1);
    }
  }

  /* Reactive book form */
  submitBookForm() {
    this.bookForm = this.fb.group({
      book_name: ['', [Validators.required]],
      isbn_10: ['', [Validators.required]],
      author_name: ['', [Validators.required]],
      publication_date: ['', [Validators.required]],
      binding_type: ['', [Validators.required]],
      in_stock: ['Yes'],
      languages: [this.languageArray]
    })
  }

  /* Get errors */
  public handleError = (controlName: string, errorName: string) => {
    return this.bookForm.controls[controlName].hasError(errorName);
  }

  /* Add dynamic languages */
  add(event: MatChipInputEvent): void {
    const input = event.input;
    const value = event.value;
    // Add language
    if ((value || '').trim() && this.languageArray.length < 5) {
      this.languageArray.push({ name: value.trim() })
    }
    // Reset the input value
    if (input) {
      input.value = '';
    }
  }
  
  /* Date */
  formatDate(e) {
    var convertDate = new Date(e.target.value).toISOString().substring(0, 10);
    this.bookForm.get('publication_date').setValue(convertDate, {
      onlyself: true
    })
  }

  /* Reset form */
  resetForm() {
    this.languageArray = [];
    this.bookForm.reset();
    Object.keys(this.bookForm.controls).forEach(key => {
      this.bookForm.controls[key].setErrors(null)
    });
  }

  /* Submit book */
  submitBook() {
    if (this.bookForm.valid){
      this.bookApi.AddBook(this.bookForm.value)
      this.resetForm();
    }
  }

}

Go to add-book.component.html and paste the following code.

 <!-- Title group  -->
 <div class="title-group">
   <h1 class="mat-h1">Add Book</h1>
   <mat-divider fxFlex="1 0"></mat-divider>
 </div>

 <!-- Book form -->
 <div class="inner-wrapper">
   <form [formGroup]="bookForm" (ngSubmit)="submitBook()" #resetBookForm="ngForm" novalidate>
     <!-- Left block -->
     <mat-card>
       <div class="controlers-wrapper">
         <!-- Book name -->
         <mat-form-field class="example-full-width">
           <input matInput placeholder="Book name" formControlName="book_name">
           <mat-error *ngIf="handleError('book_name', 'required')">
             You must provide a<strong>book name</strong>
           </mat-error>
         </mat-form-field>

         <!-- ISBN -->
         <mat-form-field class="example-full-width">
           <input matInput placeholder="ISBN-10" formControlName="isbn_10" pattern="[0-9]*" minlength="10"
             maxlength="10">
           <mat-error *ngIf="handleError('isbn_10', 'required')">
             You must provide a <strong>10 digit ISBN</strong>
           </mat-error>
           <mat-error *ngIf="handleError('isbn_10', 'pattern')">
             Only numbers are allowed
           </mat-error>
           <mat-error *ngIf="handleError('isbn_10', 'minlength')">
             Your <strong>ISBN</strong> must be 10 digit
           </mat-error>
         </mat-form-field>

         <!-- Author name -->
         <mat-form-field class="example-full-width">
           <input matInput placeholder="Author name" formControlName="author_name">
           <mat-error *ngIf="handleError('author_name', 'required')">
             You must provide an <strong>author name</strong>
           </mat-error>
         </mat-form-field>

         <!-- Publication date -->
         <mat-form-field>
           <input matInput readonly [matDatepicker]="picker" placeholder="Publication date"
             formControlName="publication_date" (dateChange)="formatDate($event)">
           <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
           <mat-datepicker #picker></mat-datepicker>
           <mat-error *ngIf="handleError('publication_date', 'required')">
             Publication date is required
           </mat-error>
         </mat-form-field>
       </div>
     </mat-card>

     <!-- Right block -->
     <mat-card>
       <div class="controlers-wrapper">
         <!-- Book binding -->
         <mat-form-field>
           <mat-label>Binding type</mat-label>
           <mat-select [(value)]="selected" formControlName="binding_type">
             <mat-option [value]="bindingType" *ngFor="let bindingType of BindingType">{{bindingType}}</mat-option>
           </mat-select>
           <mat-error *ngIf="handleError('binding_type', 'required')">
             Binding type is required
           </mat-error>
         </mat-form-field>

         <!-- Book stock -->
         <div class="misc-bottom-padding">
           <mat-label>Available in stock: </mat-label>
           <mat-radio-group aria-label="Select an option" formControlName="in_stock">
             <mat-radio-button value="Yes">Yes</mat-radio-button>
             <mat-radio-button value="No">No</mat-radio-button>
           </mat-radio-group>
         </div>

         <!-- Add languages -->
         <mat-form-field class="multiple-items">
           <mat-chip-list #chipList>
             <mat-chip *ngFor="let lang of languageArray" [selectable]="selectable" [removable]="removable"
               (removed)="remove(lang)">
               {{lang.name}}
               <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
             </mat-chip>
             <input placeholder="Add languages" [matChipInputFor]="chipList"
               [matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="addOnBlur"
               (matChipInputTokenEnd)="add($event)">
           </mat-chip-list>
           <i class="material-icons tooltip-info" matTooltip="Enter item name and press enter to add multiple items">
             info
           </i>
         </mat-form-field>
       </div>
     </mat-card>

     <!-- Submit & Reset -->
     <mat-card>
       <div class="full-wrapper button-wrapper">
         <div class="button-wrapper">
           <button mat-flat-button color="warn">Submit</button>
           <button mat-flat-button color="war" (click)="resetForm()">Clear</button>
         </div>
       </div>
     </mat-card>
   </form>
 </div>

08. Angular 7 Firebase Tutorial: Show Books Data and Delete Book using Angular Material 7 and Angular 7 Service

We will fetch the book data from Firebase database and show it to front-end using Angular Material data table. By using the given below code we’ll complete the following tasks.

  • How to use Angular Material data table in Angular 7 web app?
  • How to implement Angular material pagination in Angular material table?
  • How to delete angular material table’s specific row?
  • How to Delete a single object from Firebase database?

Go to book-list.component.ts file and import the following services.

import { Book } from './../../shared/book';
import { Component, ViewChild } from '@angular/core';
import { MatPaginator, MatTableDataSource } from '@angular/material';
import { BookService } from './../../shared/book.service';

@Component({
  selector: 'app-book-list',
  templateUrl: './book-list.component.html',
  styleUrls: ['./book-list.component.css']
})

export class BookListComponent {
  
  dataSource: MatTableDataSource<Book>;
  @ViewChild(MatPaginator) paginator: MatPaginator;
  BookData: any = [];
  displayedColumns: any[] = [
    '$key',
    'book_name',
    'author_name', 
    'publication_date',
    'in_stock',
    'action'
  ];
  
  constructor(private bookApi: BookService){
    this.bookApi.GetBookList()
    .snapshotChanges().subscribe(books => {
        books.forEach(item => {
          let a = item.payload.toJSON();
          a['$key'] = item.key;
          this.BookData.push(a as Book)
        })
        /* Data table */
        this.dataSource = new MatTableDataSource(this.BookData);
        /* Pagination */
        setTimeout(() => {
          this.dataSource.paginator = this.paginator;
        }, 0);
    })
  }

  /* Delete */
  deleteBook(index: number, e){
    if(window.confirm('Are you sure?')) {
      const data = this.dataSource.data;
      data.splice((this.paginator.pageIndex * this.paginator.pageSize) + index, 1);
      this.dataSource.data = data;
      this.bookApi.DeleteBook(e.$key)
    }
  }
  
}

Go to book-list.component.html file and import the following services.

<!-- Title group  -->
<div class="title-group">
  <h1 class="mat-h1">Book List</h1>
  <mat-divider fxFlex="1 0"></mat-divider>
</div>

<!-- No data message -->
<p *ngIf="BookData.length <= 0" class="no-data">There is no data added yet!</p>

<!-- Books list Angular material data table -->
<div class="container" *ngIf="BookData.length > 0">
  <div class="mat-elevation-z8">
    <table mat-table [dataSource]="dataSource">
      <ng-container matColumnDef="$key">
        <th mat-header-cell *matHeaderCellDef> Book ID </th>
        <td mat-cell *matCellDef="let element"> {{element.$key}} </td>
      </ng-container>

      <ng-container matColumnDef="book_name">
        <th mat-header-cell *matHeaderCellDef> Book Name </th>
        <td mat-cell *matCellDef="let element"> {{element.book_name}} </td>
      </ng-container>

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

      <ng-container matColumnDef="publication_date">
        <th mat-header-cell *matHeaderCellDef> Publication Date </th>
        <td mat-cell *matCellDef="let element"> {{element.publication_date}} </td>
      </ng-container>

      <ng-container matColumnDef="in_stock">
        <th mat-header-cell *matHeaderCellDef> In Stock </th>
        <td mat-cell *matCellDef="let element"> {{element.in_stock}} </td>
      </ng-container>

      <ng-container matColumnDef="action">
        <th mat-header-cell *matHeaderCellDef> Action </th>
        <td mat-cell *matCellDef="let element; let i = index;">
          <button mat-raised-button color="primary" class="push-right"
            [routerLink]="['/edit-book/', element.$key]">Edit</button>
          <button mat-raised-button color="accent" (click)="deleteBook(i, element)">Delete</button>
        </td>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
      <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
    </table>

    <mat-paginator [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons></mat-paginator>
  </div>
</div>

09. Angular 7 Firebase Tutorial: Edit a Book using Angular Material 7

To create edit functionality we have to create an edit button and bind it to click event in edit book component.

Go to edit-book > edit-book.component.ts file and add the following imports and Language interface class.

import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from "@angular/router";
import { Location } from '@angular/common';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material';
import { BookService } from './../../shared/book.service';
import { FormGroup, FormBuilder, Validators } from "@angular/forms";

export interface Language {
  name: string;
}

@Component({
  selector: 'app-edit-book',
  templateUrl: './edit-book.component.html',
  styleUrls: ['./edit-book.component.css']
})

export class EditBookComponent implements OnInit {
  visible = true;
  selectable = true;
  removable = true;
  addOnBlur = true;
  languageArray: Language[] = [];
  @ViewChild('chipList') chipList;
  readonly separatorKeysCodes: number[] = [ENTER, COMMA];
  selectedBindingType: string;
  editBookForm: FormGroup;
  BindingType: any = ['Paperback', 'Case binding', 'Perfect binding', 'Saddle stitch binding', 'Spiral binding'];

  ngOnInit() {
    this.updateBookForm();
  }

  constructor(
    public fb: FormBuilder,    
    private location: Location,
    private bookApi: BookService,
    private actRoute: ActivatedRoute,
    private router: Router
  ) { 
    var id = this.actRoute.snapshot.paramMap.get('id');
    this.bookApi.GetBook(id).valueChanges().subscribe(data => {
      this.languageArray = data.languages;
      this.editBookForm.setValue(data);
    })
  }

  /* Update form */
  updateBookForm(){
    this.editBookForm = this.fb.group({
      book_name: ['', [Validators.required]],
      isbn_10: ['', [Validators.required]],
      author_name: ['', [Validators.required]],
      publication_date: ['', [Validators.required]],
      binding_type: ['', [Validators.required]],
      in_stock: ['Yes'],
      languages: ['']
    })
  }

  /* Add language */
  add(event: MatChipInputEvent): void {
    var input: any = event.input;
    var value: any = event.value;
    // Add language
    if ((value || '').trim() && this.languageArray.length < 5) {
      this.languageArray.push({name: value.trim()});
    }
    // Reset the input value
    if (input) {
      input.value = '';
    }
  }

  /* Remove language */
  remove(language: any): void {
    const index = this.languageArray.indexOf(language);
    if (index >= 0) {
      this.languageArray.splice(index, 1);
    }
  }

  /* Get errors */
  public handleError = (controlName: string, errorName: string) => {
    return this.editBookForm.controls[controlName].hasError(errorName);
  }

  /* Date */
  formatDate(e) {
    var convertDate = new Date(e.target.value).toISOString().substring(0, 10);
    this.editBookForm.get('publication_date').setValue(convertDate, {
      onlyself: true
    })
  }

  /* Go to previous page */
  goBack(){
    this.location.back();
  }

  /* Submit book */
  updateBook() {
    var id = this.actRoute.snapshot.paramMap.get('id');
    if(window.confirm('Are you sure you wanna update?')){
        this.bookApi.UpdateBook(id, this.editBookForm.value);
      this.router.navigate(['books-list']);
    }
  }

}

Also include the following code in the edit-book.component.html file.

<!-- Title group  -->
<div class="title-group">
  <h1 class="mat-h1">Edit Book</h1>
  <mat-divider fxFlex="1 0"></mat-divider>
</div>

<!-- Book form -->
<div class="inner-wrapper">
  <form [formGroup]="editBookForm" (ngSubmit)="updateBook()" novalidate>
    <mat-card>
      <div class="controlers-wrapper">
        <!-- Book name -->
        <mat-form-field class="example-full-width">
          <input matInput placeholder="Book name" formControlName="book_name">
          <mat-error *ngIf="handleError('book_name', 'required')">
            You must provide a<strong>book name</strong>
          </mat-error>
        </mat-form-field>

        <!-- ISBN -->
        <mat-form-field class="example-full-width">
          <input matInput placeholder="ISBN-10" formControlName="isbn_10" pattern="[0-9]*" minlength="10"
            maxlength="10">
          <mat-error *ngIf="handleError('isbn_10', 'required')">
            You must provide a <strong>10 digit ISBN</strong>
          </mat-error>
          <mat-error *ngIf="handleError('isbn_10', 'pattern')">
            Only numbers are allowed
          </mat-error>
          <mat-error *ngIf="handleError('isbn_10', 'minlength')">
            Your <strong>ISBN</strong> must be 10 digit
          </mat-error>
        </mat-form-field>

        <!-- Author name -->
        <mat-form-field class="example-full-width">
          <input matInput placeholder="Author name" formControlName="author_name">
          <mat-error *ngIf="handleError('author_name', 'required')">
            You must provide an <strong>author name</strong>
          </mat-error>
        </mat-form-field>

        <!-- Publication date -->
        <mat-form-field>
          <input matInput readonly [matDatepicker]="picker" placeholder="Publication date"
            formControlName="publication_date" (dateChange)="formatDate($event)">
          <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
          <mat-datepicker #picker></mat-datepicker>
          <mat-error *ngIf="handleError('publication_date', 'required')">
            Publication date is required
          </mat-error>
        </mat-form-field>
      </div>
    </mat-card>

    <mat-card>
      <div class="controlers-wrapper">
        <!-- Book binding -->
        <mat-form-field>
          <mat-label>Binding type</mat-label>
          <mat-select [(value)]="selected" formControlName="binding_type">
            <mat-option [value]="bindingType" *ngFor="let bindingType of BindingType">{{bindingType}}</mat-option>
          </mat-select>
          <mat-error *ngIf="handleError('binding_type', 'required')">
            Binding type is required
          </mat-error>
        </mat-form-field>

        <!-- Book stock -->
        <div class="misc-bottom-padding">
          <mat-label>Available in stock: </mat-label>
          <mat-radio-group aria-label="Select an option" formControlName="in_stock">
            <mat-radio-button value="Yes">Yes</mat-radio-button>
            <mat-radio-button value="No">No</mat-radio-button>
          </mat-radio-group>
        </div>

        <!-- Add languages -->
        <mat-form-field class="multiple-items">
          <mat-chip-list #chipList>
            <mat-chip *ngFor="let lang of languageArray" [selectable]="selectable" [removable]="removable"
              (removed)="remove(lang)">
              {{lang.name}}
              <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
            </mat-chip>
            <input placeholder="Add languages" [matChipInputFor]="chipList"
              [matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="addOnBlur"
              (matChipInputTokenEnd)="add($event)">
          </mat-chip-list>
          <i class="material-icons tooltip-info" matTooltip="Enter item name and press enter to add multiple items">
            info
          </i>
        </mat-form-field>
      </div>
    </mat-card>

    <!-- Submit & Reset -->
    <mat-card>
      <div class="full-wrapper button-wrapper">
        <div class="button-wrapper">
          <button mat-flat-button color="warn">Update</button>
          <button mat-flat-button color="war" type="button" (click)="goBack()">Go Back</button>
        </div>
      </div>
    </mat-card>
  </form>
</div>

10. Run Angular & Firebase 6 CRUD Web Application

Open your terminal and enter the following command to start your project.

ng serve

Check out the demo of Angular 7 Firebase 6 CRUD web app with Angular Material 7.

Finally, we have created the basic Angular 7 Firebase 6 CRUD web app with Angular Material 7. For better project understanding, you can check out the GitHub repo of this project.

Some useful tutorials:

Digamber Rawat
Digamber Rawat

Full stack developer with a passion for UI/UX design. I create beautiful and useful digital products to solve people’s problem and make their life easy.