Angular Firebase CRUD operations using angularFire 7.2.0

In this tutorial, we’ll learn and demonstrate the latest AngularFire 7.2.0, to perform Angular Firebase CRUD operation. The Firebase is one of the most popular and used serverless technology from Google. Allows front-end developers to focus mostly on the frontend instead of on the backend. The Google Firebase offers a wide range of backend services like hosting, cloud Firestore, Analytics, cloud messaging, cloud storage, and many more.

What we are learning on Angular Firebase?

In this Angular Firebase tutorial, we will be focusing on Angular Firestore and will perform the following Firebase CRUD operation using the latest angularFire library.

  1. Retrieving a collection of records from a collection called books.
  2. Adding a new record to books collection.
  3. Deleting/editing a document or record.
  4. Editing a single field of a document like the price of a book on blur event on input.
  5. Performing firebase query, search by a field
  6. Retrieving a document by id

Firebase CRUD App Screenshot of our Angular book apps.

angular firebase crud
Angular Firebase CRUD example

We can use Firebase Firestore to store data on Google cloud storage. With Firebase we can easily do CRUD operations and Firebase also provides a different option for authentication for Firebase CRUD apps.

Setting up and configuring Firebase CRUD apps

Before using Firebase, we need to configure our application. Let’s first create Firebase angular project.

ng new firebaseCRUDApp
cd firebaseCRUDApp

Setting Firebase project is simple, but it has some steps to be carried out. We’ll divide the configuration section into three parts, otherwise, it will messy and long.

  1. Creating Firebase project and configuring project at Firebase console.
  2. Installing AngularFire and adding Bootstrap UI and icon.
  3. Creating components, services to implement Firebase activities

Create and configure our project in the Firebase console.

Let open the browser and type https://firebase.google.com, we need to create a project on firebase and configure the Google cloud Firestore database for it.

Angular firebase CRUD

In the Firebase console, we need to click on Add Project to create a new project and we should give the name of our project, In our case let say book as a project name.

Angular firebase CRUD example

Once we add our project name, let’s click on the continue button, it will take us to these options. For this example we don’t need Google Analytics, let’s disable it.

Angular firestore operation

Clicking on the Create project will create a Firebase project for us.

Create a Firebase web project

Now we need Firebase configuration for our web application. Our front-end application needs the web configuration to connect to the Firebase project at Google Firestore.

In the Firebase project dashboard, we have many options and let’s click on the “Web” icon as shown in the image below.

Firebase web config

Once, we click on the web icon, we have to provide a nickname for our app. You can use any name of your choice. Here we will use the name bookWebApp, the same name as ours. Click on the “Register App” button.

Angular firestore example

Add Firebase web config value in the Front-end project

We need to copy this our firebase project API key, and past it in our client project src/environments/environment.ts as shown in the code snippet below. The below code is just for demo, you can replace it with your own API code.

export const environment = {
  production: false,
  firebase: {
    apiKey: "AIzffdfdf--jSe9ys",
    authDomain: "book-sss.firebaseapp.com",
    projectId: "book-sss4db",
    storageBucket: "book-8db.appspot.com",
    messagingSenderId: "98759698508",
    appId: "1:959685345:web:30a3c6bf6d8fca5837b6cc"
  }
};
Note : The above configuration is dummy, you have to replace your Firebase config value, which you get from Firebase project.

Create cloud firestore database

We have almost finished our firebase project configure in firebase.com, now we need to create our firebase database. Let’s click on the Firestore database icon as shown below.

Create Firebase project

Click on create database will ask us for production or test mode and this is an example. Let’s select test mode, now our database is ready. In later articles, we will learn how to perform Firebase operations using AngularFire.

Installing AngularFire and Adding the Bootstrap library

We had successfully set up the Firebase project at the Firebase cloud console. Let’s now add Bootstrap and bootstrap icon libraries to our project.

ng add @ng-bootstrap/ng-bootstrap
npm install bootstrap-icons --save

To use bootstrap icons we need to import, the Bootstrap-icon style in style.scss or style.css file.

/* Importing Bootstrap SCSS file. */
@import '~bootstrap/scss/bootstrap';
@import '~bootstrap-icons/font/bootstrap-icons';

Install AngularFire library to our project

We need to install the angularFire library to perform the Firebase CRUD operation in our Angular application. The angularFire is the Angular library for Firebase, the latest AngularFire 7 has lots of new changes, we can easily perform Firebase CRUD operations using it.

Let’s run the command below to add the Firebase project to our Angular project

ng add @angular/fire

While running the above command will take a few minutes, asking you to select some options as shown in the image below.

AngularFire add command
AngularFire Command
  1. First, we have to select what feature we want to add to our project, for us we have to select Firestore.
  2. On select Firestore, will ask you to login to Firebase and if you are already connected then it will take automatically login.
  3. At last, we have to select the project name, in our case Firebase console project books, select it.

Once we successfully add the AngularFire library, we need to set the Firebase configuration in the app.module.ts file to connect to the Firebase project in the Google Firebase cloud.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
import { getFirestore, provideFirestore } from '@angular/fire/firestore';

import { environment } from 'src/environments/environment';
import { FormsModule } from '@angular/forms';


@NgModule({
  declarations: [
    AppComponent,
    BooksComponent,
    BookComponent,
    HomeComponent,
    EditBookComponent,
    SearchComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    NgbModule,
    provideFirebaseApp(() => initializeApp(environment.firebase)),
    provideFirestore(() => getFirestore()),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

We also need Forms, so we had added FormsModule to use the Angular template form for adding and editing a book record. The Firebase configuration setting is different from the previous version of AngularFire, where we use AngularFireModule, this is not the case in the latest version.

We have to use the provideFirebaseApp and provideFireStore function to connect to our Firebase project.

Creating all components, book service for Firebase CRUD

To implement the firebase CRUD operation, we will need a form that allows us to add/edit book records. All book record activities like adding a new book, deleting, updating, and listing are done in our book service, which is consumed by components. Let’s add all components needed for Firebase CRUD operation.

ng generate component components/home
ng generate component components/book
ng generate component components/books
ng generate component components/search

In our home component contains two parts.

  1. Book component for adding new book records
  2. Books component, list of books already added.

We also need the book service, and modal called edit-book modal to implement editing of a record.

ng generate component modal/editBook
ng generate service services/book

We also need a model for the book, so that we can implement a strict type, to minimize error. Let create folder models in src/app and add a file called book.model.ts file.

export interface IBook {
    id?: string;
    name : string;
    author : string;
    genre : string;
    price : number;
}

Configure routing of Firestore application, we have two navigation buttons, home and search component. In the app.routing.module.ts, add routing configuration for both.

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { SearchComponent } from './components/search/search.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'search', component: SearchComponent }
];

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

The home component has two child components, one for adding new records and the other for listing all records. Let’s edit the home.component.html file

<div class="container">
    <div class="row">
        <div class="col-md-4">
            <app-book></app-book>
        </div>
        <div class="col-md-7">
            <app-books></app-books>
        </div>
    </div>
</div>

In the app.component.html template, let’s add the navigation part.

<div class="jumbotron">
  <h2 style="text-align:center;">
    Book Collection
  </h2>
  <button type="button" routerLink="" class="btn btn-link">
    Home
   </button>
  <button type="button" routerLink="search" class="btn btn-link">
     Search
  </button>
</div>

<router-outlet></router-outlet>

Angular Firestore CRUD operation

Let implement Angular Firestore CRUD operation, as we had completed all configuration and setting up of the Firebase project. The Firestore CRUD stands for (Create, read, update and delete).

Firebase Create a record

We’ll be implementing one by one CRUD operation, let first perform creating a new record in Firebase. In our services/book.service.ts edit code to implement create operation.

import { Injectable } from '@angular/core';
import {
  Firestore, addDoc, collection, collectionData,
  doc, docData, deleteDoc, updateDoc, DocumentReference, setDoc
} from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import { IBook } from '../models/book.model';

@Injectable({
  providedIn: 'root'
})
export class BookService {

  constructor(private firestore: Firestore) { }

  addBook(book: IBook) {
    const booksRef = collection(this.firestore, 'books'); 
    return addDoc(booksRef, book);
  }
}

We need to import Firestore from ‘@angular/fire/firestore’ and also need to declare it in the constructor. We also import many objects and classes from @angular/fire/firestore, we need all this later.

First, we need a reference to our collection books, which we can get from a collection by passing two-argument the Firestore and the name of the collection in our case books. Now we use addDoc to add create a new record book using the reference of a collection as shown above.

Create a form for adding new book record

We need a form, to add a new book record, here is a screenshot for adding a record in Firestore.

Angular firestore create operation

In the book.component.ts file, we need to import BookService and inject it into the book constructor. Let edit the book component to consume the BookService to add a new record.

import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { IBook } from 'src/app/models/book.model';
import { BookService } from 'src/app/services/book.service';

@Component({
  selector: 'app-book',
  templateUrl: './book.component.html',
  styleUrls: ['./book.component.scss']
})
export class BookComponent implements OnInit {

  book: IBook = { name: '', author: '', genre: '', price: 0 };

  constructor(private bookService: BookService) { }

  ngOnInit() {
  }

  onSubmit(form: NgForm) {
    this.bookService.addBook(form.value).
      then(() => form.reset());
  }
}

Let add a template form, to allow users to input a book record.

<div>
  <h4>Add new book record</h4>
  <form #bookForm="ngForm" (ngSubmit)="onSubmit(bookForm)">
    <div class="form-group">
       <label>Book Name</label>
       <input class="form-control" name="name" #name="ngModel" 
        [(ngModel)]="book.name" placeholder="Book Name" required>
    </div>

    <div class="form-group">
      <label>Author</label>
      <input class="form-control" name="author" #name="ngModel" 
        [(ngModel)]="book.author" placeholder="Author" required>
    </div>

     <div class="form-group">
       <label>Genre</label>
       <select class="form-control" name="genre" #name=ngModel 
         [(ngModel)]="book.genre">
          <option value="Novel">Novel</option>
          <option value="Non Fiction">Non Fiction</option>
          <option value="Biography">Biography</option>
       </select>
      </div>

      <div class="form-group">
        <label>Price</label>
        <div class="input-group">
           <div class="input-group-addon">
               <i class="bi bi-currency-dollar"></i>
           </div>
           <input class="form-control" name="price" #name="ngModel" 
             [(ngModel)]="book.price" placeholder="Price" required>
            </div>
        </div>

        <div class="form-group float-right">
           <button type="submit" class="btn btn-success" 
             [disabled]="!bookForm.valid" [disabled]="bookForm.invalid">
              <i class="bi bi-plus"></i>Add Book</button>
        </div>
    </form>
</div>

Clicking on submit button will add a record to Firestore, It is best to keep API access outside of the component. We use service to access API calls to Firebase, service acts as state management allows us to share service code among different components.

Retrieve Firestore data collection

In the books component, we are displaying all book records of collection books. Let first edit the book.service.ts file to retrieve all documents from a collection.

getBooks(): Observable<IBook[]> {
   const booksRef = collection(this.firestore, 'books');
   return collectionData(booksRef, { idField: 'id' }) as Observable<IBook[]>;
 }

We use collectionData to get all records from collections, to get documents with the id we need to add { idField: ‘id’ }. In the previous version of AngularFire is a little bit of extra work, now getting documents with id is easy. The getBooks() return observable of type IBook array, we are consuming this observable in books component.

Angular firestore get a collection

Now in the books.component.ts file, we need to import and inject BookService to consume the getBooks() method from BookService and let edit this component.

import { Component, OnInit } from '@angular/core';
import { IBook } from 'src/app/models/book.model';
import { BookService } from 'src/app/services/book.service';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { EditBookComponent } from '../modal/edit-book/edit-book.component';

@Component({
  selector: 'app-books',
  templateUrl: './books.component.html',
  styleUrls: ['./books.component.scss']
})
export class BooksComponent implements OnInit {
  books: IBook[] = [];

  constructor(
    private bookService: BookService,
    private modal: NgbModal) { }

  ngOnInit() {
    this.bookService.getBooks().subscribe((res: IBook[]) => {
      this.books = res;
    })
  }

  editModal(book: IBook) {
    const modalRef = this.modal.open(EditBookComponent, {
      size: 'lg',
      centered: true,
      windowClass: 'dark-modal',
    });

    modalRef.componentInstance.id = book.id;
  }

  deleteBook(book: IBook) {
    if (confirm('Are you sure to delete this record ?') == true) {
      this.bookService.deleteBook(book).then(() => 
       console.log('delete successful'));
    }
  }
}

In this component typescript, we also have code to edit and delete records. For time being let only focus on ngOnit to get books record. Now in the book.component.html template, let add a table to display the book’s records.

<table class="table table-striped">
  <thead>
   <tr>
     <th scope="col">#</th>
     <th scope="col">Name</th>
     <th scope="col">Author</th>
     <th scope="col">Genres</th>
     <th scope="col">Price</th>
     <th scope="col">Edit</th>
     <th scope="col">Delete</th>
   </tr>
  </thead>
  <tbody *ngIf="books">
    <tr *ngFor="let book of books; index as i">
      <th scope="row">{{ i+1 }}</th>
      <td>{{ book.name }}</td>
      <td>{{ book.author }}</td>
      <td>{{ book.genre }}</td>
      <td>{{ book.price }}</td>
      <td>
        <i class="bi bi-pencil-square" style="color: green;" 
           (click)="editModal(book)"></i>
      </td>
      <td>
        <i class="bi bi-trash" (click)="deleteBook(book)" style="color: red;"></i>
      </td>
    </tr>
   </tbody>
</table>

Firestore CRUD delete operation

In the books component, we have a list of all books from the collection books. We also add pencil icons to edit and delete icons to delete on each book on the list. Clicking on the delete icon will invoke delete a record on the collection list. But first, we need to add the corresponding delete function in the book.service.ts file, let’s add it.

deleteBook(book: IBook) {
    const bookDocRef = doc(this.firestore, `books/${book.id}`);
    return deleteDoc(bookDocRef);
}

The consumer of this the deleteBook() method is already added in the previous section books component. We only have to pass the id of the book record to delete.

Firebase CRUD update and set operation

We have completed CRUD operations like create, delete and read. But now we are left with an update and set operation of a record. Let do this in our edit-book modal component, as shown in the image below.

Angular Firebase CRUD operation update

Before implementing the edit book modal, we need to add it corresponding update and set method in the book.service.ts file. Let add set, update and retrieve the Firebase document by id operations in the book.service.ts file.

getBookByID(id: string) {
  const bookRef = doc(this.firestore, `books/${id}`);
  return docData(bookRef, { idField: 'id' }) as Observable<IBook>;
}

updateBook(book: IBook) {
  const bookDocRef = doc(this.firestore, `books/${book.id}`);
  return setDoc(bookDocRef, book);
}

modifyBookPrice(book: IBook, amount: number) {
  const bookDocRef = doc(this.firestore, `books/${book.id}`);
  return updateDoc(bookDocRef, { price: amount });
}
setDocupdateDoc
Destructively updates a document’s data, if the document didn’t exist it will create a new document.Non-destructively updates a document’s data. Edit field or property of the document.

In the books component, we have an edit icon, and clicking on it will invoke editModal(book: IBook) which in turn opens modal for edit book with an id of that book.

Now in the edit book modal, we are performing the following activities.

  • On blur on price input will set new price value for book record.
  • Clicking on Update Book will update the book to a new value that we have entered in a form.

Let’s edit the edit-book.component.ts file, we also need to retrieve a book record by using its id

import { Component, Input, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { IBook } from 'src/app/models/book.model';
import { BookService } from 'src/app/services/book.service';

@Component({
  selector: 'app-edit-book',
  templateUrl: './edit-book.component.html',
  styleUrls: ['./edit-book.component.scss']
})
export class EditBookComponent implements OnInit {
  @Input() id: string;
  book: IBook;

  constructor(
    private bookService: BookService,
    public activeModal: NgbActiveModal)
     { }

  ngOnInit() {
    if (this.id)
      this.bookService.getBookByID(this.id).subscribe(res => {
        this.book = res
      });
  }

  onUpdate() {
    this.bookService.updateBook(this.book).then(() => {
      this.activeModal.close();
      console.log('Data add successfully');
    })
  }

  setPrice(book: IBook, price: number) {
    this.bookService.modifyBookPrice(book, price)
  }
}

We need to inject BookService in the edit-book component, now let’s edit the edit-book component template to add a form for edit and set.

<div *ngIf="book" class="container">
  <h4>Edit book record</h4>
  <form #bookForm="ngForm" (ngSubmit)="onUpdate()">
    <div class="form-group">
      <label>Book Name</label>
      <input class="form-control" name="name" #nameCtrl="ngModel" 
        [(ngModel)]="book.name" placeholder="Book Name" required>
    </div>

    <div class="form-group">
      <label>Author</label>
      <input class="form-control" name="author" #authorCtrl="ngModel" 
        [(ngModel)]="book.author" placeholder="Author" required>
    </div>

    <div class="form-group">
      <label>Genre</label>
      <select class="form-control" name="genre" #genreCtrl=ngModel 
        [(ngModel)]="book.genre">
        <option value="Novel">Novel</option>
        <option value="Non Fiction">Non Fiction</option>
        <option value="Biography">Biography</option>
      </select>
    </div>

    <div class="form-group">
      <label>Price</label>
      <div class="input-group">
        <div class="input-group-addon">
           <i class="bi bi-currency-dollar"></i>
        </div>
        <input class="form-control" name="price" #priceCtrl="ngModel" 
           (blur)="setPrice(book, priceCtrl.value)"
           [(ngModel)]="book.price" placeholder="Price" required>
       </div>
    </div>

    <div class="form-group float-right">
     <button type="submit" class="btn btn-success" [disabled]="!bookForm.valid">
        <i class="bi bi-plus"></i>Update Book</button>
     </div>
  </form>
</div>

Angular firebase search

Many of my visitors are requesting me to make an Angular firebase search operation, I didn’t make it till now because I didn’t find any perfect search solution from @angular/fire/firestore. Some of my projects I am using both @angular/fire/firestore and @angular/fire/compat/firestore. If you find any code that performs search queries using @angular/fire/firestore, please let me know and I’m also looking at it. Let’s perform an Angular firebase search using @angular/fire/compat/firestore. Here is our screenshot of the search operation.

Angular firebase search

What we are achieving?

  1. We are using rxjs combineLatest as an array of observables.
  2. We are performing OR search operation on three fields book names, genre, and authors using @angular/fire/compat/firestore.
  3. We need to import AngularFireModule from ‘@angular/fire/compat’.

Let first create searchComponent in src/components folder and add search params and code for the search operation.

import { Component } from '@angular/core';
import { AngularFirestore } from '@angular/fire/compat/firestore';
import { combineLatest, map } from 'rxjs';
import { IBook } from 'src/app/models/book.model';

@Component({
  selector: 'app-search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.scss'],
})
export class SearchComponent {
  books: IBook[] = [];
  isSearchEmpty: boolean;
  searchParams = {
    name: null,
    genre: null,
    author: null,
  };

  constructor(private afsCompact: AngularFirestore) {}

  onSearchBook() {
    this.books = [];
    const $name = this.afsCompact
      .collection('books', (ref) =>
        ref.where('name', '==', this.searchParams.name)
      )
      .valueChanges({ idField: 'id' });

    const $genre = this.afsCompact
      .collection('books', (ref) =>
        ref.where('genre', '==', this.searchParams.genre)
      )
      .valueChanges({ idField: 'id' });

    const $author = this.afsCompact
      .collection('books', (ref) =>
        ref.where('author', '>=', this.searchParams.author)
      )
      .valueChanges({ idField: 'id' });

    combineLatest([$name, $genre, $author])
      .pipe(map(([one, two, three]) => [...one, ...two, ...three]))
      .subscribe((response: any) => {
        this.books = response;
        if (response.length > 0) {
        } else {
          this.isSearchEmpty = true;
        }
      });
  }
}

Above is a simple example, if your result record depends on another collection, then you have to make an array of promises to retrieve the corresponding document from a collection.

Let’s add form in our search component template, let’s edit the src/components.search.component.html

<div class="container">
  <section>
    <h4>Search firbase record</h4>
    <div class="row grid-title">
      <div class="col-3 col-md-3">
        <div class="form-group">
          <label>Book Name</label>
          <input
            class="form-control"
            name="name"
            #name="ngModel"
            [(ngModel)]="searchParams.name"
            placeholder="Book Name"
            required
          />
        </div>
      </div>
      <div class="col-3 col-md-3">
        <div class="form-group">
          <label>Book Genre</label>
          <input
            class="form-control"
            name="gener"
            #gener="ngModel"
            [(ngModel)]="searchParams.genre"
            placeholder="Book Genre"
            required
          />
        </div>
      </div>
      <div class="col-3 col-md-3">
        <div class="form-group">
          <label>Book Author</label>
          <input
            class="form-control"
            name="price"
            #price="ngModel"
            [(ngModel)]="searchParams.author"
            placeholder="Book author"
            required
          />
        </div>
      </div>
      <div class="col-3 col-md-3">
        <button class="btn btn-primary" (click)="onSearchBook()">Search</button>
      </div>
    </div>
  </section>
  <section *ngIf="books">
    <table class="table table-striped">
      <thead>
        <tr>
          <th scope="col">#</th>
          <th scope="col">Name</th>
          <th scope="col">Author</th>
          <th scope="col">Genres</th>
          <th scope="col">Price</th>
        </tr>
      </thead>
      <div *ngIf="!isSearchEmpty; else noSearch">
        <tbody>
          <tr *ngFor="let book of books; index as i">
            <th scope="row">{{ i + 1 }}</th>
            <td>{{ book.name }}</td>
            <td>{{ book.author }}</td>
            <td>{{ book.genre }}</td>
            <td>{{ book.price }}</td>
          </tr>
        </tbody>
      </div>
    </table>

    <ng-template #noSearch> Search result not found ! </ng-template>
  </section>
</div>

At last, we need to import

....
import { AngularFireModule } from '@angular/fire/compat';


@NgModule({
  declarations: [
   ...
  imports: [
    BrowserModule,
     ...
    provideFirebaseApp(() => initializeApp(environment.firebase)),
    provideFirestore(() => getFirestore()),
    provideAuth(() => getAuth()),
    AngularFireModule.initializeApp(environment.firebase),
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Conclusion

Now we had completed the Angular Firestore CRUD operation, as this tutorial becomes lengthy, we had covered the Angular Firestore query and search operation. I hope you got some idea, of how to use AngularFire latest.

Related Post

Spread the love

8 thoughts on “Angular Firebase CRUD operations using angularFire 7.2.0”

  1. Thanks a lot!!! This has been really helpful for me. It is one of the few resources on the net that deal with AngularFire 7. I checked today (4th, feb, 2022) and they still have not posted new docs for this version. I would really appreciate if they provided better docs (given it is official Google). I am new to Firebase so I need detailed instructions. If you know about any other great resources on AngularFire 7 (and 6 as well), please drop a link. Keep the good work!

  2. Muchas gracias por el tutorial bien explicado, no hay mucha documentación con fire 7.2
    por favor si nos puedes pasar solo el método service de la búsqueda.
    Lo revise 14 de febrero 2022

Leave a Comment

Your email address will not be published.

Scroll to Top