How to do CRUD with Query operations in Firebase with Firestore.

How to do CRUD with Query operations in Firebase with Firestore.

[Bonus: You'll learn to create Angular Model, Service and Component].
CRUD — Create, Read, Update, Delete operations in Angular applications or Ionic app using Firebase with Firestore. The database is a Firestore database that exists on the cloud.

📥 Download Source Code: Clone GitHub Repository👆.
📺 Check Demo: Click here for Demo👆.


Okay, follow me while I’ll show you CRUD in Firebase. Next, Advance Query Operations in Firebase Firestore.


#1 Step - Setup Firebase

In our project, first we need to add Firebase with Firestore.
(to do that Click here👆)

Okay, I assume you have followed each step from above link↑ to setup Firebase with Firestore.

#2 Step - Create an Angular Model

After setting up Firebase with Firestore in our project, we can proceed with creating a model class. In this example, we suppose that we are creating a Model class for an Employee.

Let's create a Model for our Employee entity as follows:
(make sure within your project root directory run below command)

ng g class model/employee --type=model

This command will generate two TypeScript file – look below👇

Next, open the src/app/model/employee.model.ts file and update it as follows:

export class Employee {
    id: string;
    name: string;
    phone: number;
    designation: string;
    salary: number;
    joinDate: Date;
    address: string;
    extraInfo: string;
}

#3 Step - Create an Angular Service

An Angular service allows us to encapsulate the code that need to be repeated in many places in our project.

Let's create a Service for our Employee as follows:
(make sure within your project root directory run below command)

ng g service service/employee

This command will generate two TypeScript file – look below👇

Next, open the src/app/service/employee.service.ts file and update code step by step:

1) At the top of employee.service.ts file import AngularFirestore and the Employee model as follows:

import { AngularFirestore } from '@angular/fire/firestore';
import { Employee } from 'src/app/model/employee.model';

2) Inject AngularFirestore in our service via its constructor:

...
export class EmployeeService {

  constructor(private firestore: AngularFirestore) { }
}

Important to know:

  • Firestore stores data in Collection.
  • We can consider a Collection as tables we have in SQL.
  • Each entry of add operation create new Document in that Collection.
  • The document stores data in the form of JSON object.

In our case:
'Employees' will be the name of collection in Firestore DB.

3) Add addEmployee() method to add new employee (new document) in the Firestore collection:

...
export class EmployeeService {

  constructor(private firestore: AngularFirestore) { }

  ...
  // this method takes an employee object and 
  // add a new employee to Firestore database collection
  addEmployee(employee: Employee) {
    // convert object of type Employee to JSON object
    // because Firestore understand JSON
    const employeeObject = {...employee};
    return this.firestore.collection('Employees').add(employeeObject);
  }
}

4) Add getEmployees() method to retrieve employees from the Firestore collection:

export class EmployeeService {

  constructor(private firestore: AngularFirestore) { }

  ...
  // this method returns list of employees document,
  // fetched from Firestore database collection
  getEmployees() {
    return this.firestore.collection('Employees').snapshotChanges();
  }
}

5) Add updateEmployee() method to update information of an employee (update document) in the Firestore collection:

...
export class EmployeeService {

  constructor(private firestore: AngularFirestore) { }

  ...
  // this method takes an employee object and
  // update an object of employee to the Firestore document
  updateEmployee(employee: Employee) {
    // convert object of type Employee to JSON object
    // because Firestore understand JSON
    const employeeObject = {...employee};
    this.firestore.doc('Employees/' + employee.id).update(employeeObject);
  }
}

6) Add deleteEmployee() method to delete an employee document in the Firestore collection:

...
export class EmployeeService {

  constructor(private firestore: AngularFirestore) { }

  ...
  // this method takes an employee Id and
  // delete an employee document from the Firestore collection
  deleteEmployee(employeeId: string) {
    this.firestore.doc('Employees/' + employeeId).delete();
  }
}

Final Code of src/app/service/employee.service.ts file:

import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { Employee } from 'src/app/model/employee.model';

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

  constructor(private firestore: AngularFirestore) { }

  // this method takes an employee object and 
  // add a new employee to Firestore database collection
  addEmployee(employee: Employee) {
    // convert object of type Employee to JSON object
    // because Firestore understand JSON
    const employeeObject = {...employee};
    return this.firestore.collection('Employees').add(employeeObject);
  }

  // this method returns list of employees document,
  // fetched from Firestore database collection
  getEmployees() {
    return this.firestore.collection('Employees').snapshotChanges();
  }

  // this method takes an employee object and
  // update an object of employee to the Firestore document
  updateEmployee(employee: Employee) {
    // convert object of type Employee to JSON object
    // because Firestore understand JSON
    const employeeObject = {...employee};
    this.firestore.doc('Employees/' + employee.id).update(employeeObject);
  }

  // this method takes an employee Id and
  // delete an employee document from the Firestore collection
  deleteEmployee(employeeId: string) {
    this.firestore.doc('Employees/' + employeeId).delete();
  }

}

We have created a 'Model' and a 'Service' so far.
And added methods to perform CRUD operations to Firebase.

Now it's time to make use of these methods.
Follow with me..


#4 Step - Create an Angular Component

We will test CRUD operations in this Component. For that we will use methods we created in Employee Service.

Let's create a Component for our Employee as follows:
(make sure within your project root directory run below command)

ng g component employee-list

This command will generate four TypeScript file – look below👇


Important

Do not forget to do Routing of this new component employee-list.
Two steps to do that:
#1. Define your component route in app-routing.module.ts file.
#2. Declare your component in app.module.ts file.

Let's do this...

#1. Open up your src/app/app-routing.module.ts file and update it as follows:

...

import { EmployeeListComponent } from './employee-list/employee-list.component';

const routes: Routes = [
  ...
  {
    path: 'employees',
    component: EmployeeListComponent
  }
];

...

#2. Open up your src/app/app.module.ts file and update it as follows:

...

import { EmployeeListComponent } from './employee-list/employee-list.component';

@NgModule({
  declarations: [
    AppComponent,
    EmployeeListComponent
  ],
  ...
})

...

Learn more in depth about 'Routing of an Angular Component'

(Click here👆)


Next, open the src/app/employee-list/employee-list.component.ts file and update it as follows:

import { Component, OnInit } from '@angular/core';
import { EmployeeService } from '../service/employee.service';
import { Employee } from '../model/employee.model';

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

  // toggleUpdateBtn variable is used to show/hide update button
  toggleUpdateBtn = false;

  // toggleSearchBtn variable is used to
  // show/hide -> search and clear-search button
  toggleSearchBtn = true;

  // emplyee object - type: Employee
  employee: Employee = new Employee();

  // array of emplyee - type: Employee
  employees: Employee[];

  constructor(private employeeService: EmployeeService) { }

  // constructor will be called first and Oninit will be called later
  // after constructor method, when component is being initialized.
  ngOnInit() {
    // we added getEmployees() method in ngOnInit()
    // so that we get data to display over template - as soon as template loads.
    this.getEmployees();
  }

  getEmployees() {
    // we call getEmployees() from EmployeeService to get list of employees
    this.employeeService.getEmployees().subscribe(data => {
      // this.employees stores list of employee
      this.employees = data.map(e => {
        return {
          id: e.payload.doc.id,
          ...e.payload.doc.data()
        } as Employee;
      });
    });
  }

  // this method takes an employee object and
  // call addEmployee() method from EmployeeService
  addEmployee(employee: Employee) {
    return this.employeeService.addEmployee(employee);
  }

  // this method takes an employee object and
  // call updateEmployee() method from EmployeeService
  updateEmployee(employee: Employee) {
    this.employeeService.updateEmployee(employee);
    // update done, hide update-button, and show submit-button
    this.toggleUpdateBtn = false;
    // also clear/reset this.employee object
    this.employee = new Employee();
  }

  // this method takes an employee Id and
  // call deleteEmployee() method from EmployeeService
  deleteEmployee(employeeId: string) {
    this.employeeService.deleteEmployee(employeeId);
  }

  // this method takes employee object and
  // set to local variable this.employee, also enable update-button
  // so that user can edit information and update to database
  editFunc(employee: Employee) {
    // set employee details to input fields
    this.employee = employee;
    // show update-button, and hide submit-button
    // so that user can change employee details
    this.toggleUpdateBtn = true;
  }

  // this method takes an employee object and
  // call searchEmployee() method from EmployeeService
  // employeeService.updateEmployee(employee) method
  // will evaluate search parameter and fetch results from
  // Firebase and return back that result will be displayed on screen
  searchEmployee(employee: Employee) {
    // we call searchEmployees(employee) from EmployeeService to get
    // list of employees matched with search params
    // we pass `employee` object which contains search params
    this.employeeService.searchEmployees(employee).subscribe(data => {
      // this.employees stores list of employee get from search result
      this.employees = data.map(e => {
        return {
          id: e.payload.doc.id,
          ...e.payload.doc.data()
        } as Employee;
      });
    });
  }

  // this function will clear search result and fetch all employee
  clearSearch() {
    // 1. clear employees list
    this.employees = [];

    // 2. get all employee
    this.getEmployees();
  }

}

In Angular – Constructor will be called first and ngOnInit will be called later after constructor method, when component is being initialized.
Learn more about difference between Constructor and ngOnInit.
(Click here👆)

Next, open the src/app/employee-list/employee-list.component.html file and update it as follows:

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-menu-button></ion-menu-button>
    </ion-buttons>
    <!-- <ion-title>
      Home
    </ion-title> -->
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <p>
    Add New Employee OR Search Employee.<br>
    <span style="color:gray;">(We'll use Firebase Query to get Search results)</span>
  </p>
  <table>
    <tbody>
      <tr>
        <td>Name</td>
        <td>
          <input type="text" [(ngModel)]="employee.name" required>
        </td>
      </tr>
      <tr>
        <td>Phone</td>
        <td>
          <input type="number" [(ngModel)]="employee.phone" required>
        </td>
      </tr>
      <tr>
        <td>Designation</td>
        <td>
          <input type="text" [(ngModel)]="employee.designation" required>
        </td>
      </tr>
      <tr>
        <td>Salary</td>
        <td>
          <input type="number" [(ngModel)]="employee.salary" required>
        </td>
      </tr>
      <tr>
        <td>JoinDate</td>
        <td>
          <input type="date" [(ngModel)]="employee.joinDate" required>
        </td>
      </tr>
      <tr>
        <td>Address</td>
        <td>
          <input type="text" [(ngModel)]="employee.address" required>
        </td>
      </tr>
      <tr>
        <td>ExtraInfo</td>
        <td>
          <input type="text" [(ngModel)]="employee.extraInfo" required>
        </td>
      </tr>
      <tr>
        <td>Add</td>
        <td>
          <!-- submit button (to create new employee) -->
          <ion-button *ngIf="!toggleUpdateBtn" (click)="addEmployee(employee)">Submit</ion-button>
          <!-- update button (to update existing employee) -->
          <ion-button *ngIf="toggleUpdateBtn" (click)="updateEmployee(employee)">update</ion-button>

          <!-- search button (find employee based on given info) -->
          <ion-button *ngIf="toggleSearchBtn" (click)="searchEmployee(employee)">Search</ion-button>
          <!-- clear search button (clear search result and display all employee)
              by calling clearSearch() -->
          <ion-button *ngIf="!toggleSearchBtn" (click)="clearSearch()">Clear Search</ion-button>
        </td>
      </tr>
    </tbody>
  </table>

  <p style="border-top: 1px solid gray;"></p>
  
  <p>
    Employee List Works!
  </p>
  <table>
    <thead>
      <th>Name</th>
      <th>Phone</th>
      <th>Designation</th>
      <th>Salary</th>
      <th>JoinDate</th>
      <th>Address</th>
      <th>ExtraInfo</th>
      <th>Update</th>
      <th>Delete</th>
    </thead>
    <tbody>
      <tr *ngFor="let employee of employees">
        <td>{{ employee.name }}</td>
        <td>{{ employee.phone }}</td>
        <td>{{ employee.designation }}</td>
        <td>{{ employee.salary }}</td>
        <td>{{ employee.joinDate }}</td>
        <td>{{ employee.address }}</td>
        <td>{{ employee.extraInfo }}</td>
        <td>
          <ion-button (click)="editFunc(employee)">Edit</ion-button>
        </td>
        <td>
          <ion-button (click)="deleteEmployee(employee.id)">Delete</ion-button>
        </td>
      </tr>
    </tbody>
  </table>
</ion-content>

Bonus 🤩 Advance Query in Firebase Firestore:

  • A) Single where statement Query.
  • B) Multiple where statement Query.
    1) Query for `multiple` where statement.
    2) Query for range '>=' operators.
    3) Query order by `ascending`.
    4) Query order by `descending` by date or strings.
    5) Apply limit to Query result.
    6) Offset by a property, suppose we want employee whose name starts with `An` then apply startAt('An')
→ It's very important to read comments of the below code file.
→ Also keep open your browser console while test this code to read query errors.

Now, open the src/app/service/employee.service.ts file and update it as follows:

import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { Employee } from 'src/app/model/employee.model';

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

  constructor(private firestore: AngularFirestore) { }
  
  /* 
   * ....
   * ....
   * ....
   * ....
   * ....
   * OTHER METHODS MAY COME HERE 
  */

  // this method takes an employee object and
  // we takes parameter from this employee object to
  // pass to firebase query, so that we get desired search results
  // later this method returns list of employees fetched from
  // Firestore Query
  searchEmployees(employee: Employee) {
    /*  #A) one way of applying search query:
        In case when we have `single` where statement to search.
        this query will result list of employee matched with
        given employee.name parameter
    */
    // 👇 un-comment this code to use single-query👇
    // return this.firestore.collection(
    //   'Employees', ref => ref.where('name', '==', employee.name)).snapshotChanges();

    /* #B) second way of applying search query:
        In case when we have `multiple` where statement to search.
        this query will result list of employee matched with
        all given parameters:
        #1. Query for `multiple` where statement.
        #2. Query for range '>=' operators.
        #3. Query order by `ascending`.
        #4. Query order by `descending` by date or strings.
        #5. Apply limit to Query result.
        #6. Offset by a property, suppose we want employee whose
            name starts with `An` then apply startAt('An')
    */

    /*
      After applying these query you may face this error:
      "ERROR FirebaseError: The query requires an index. You can create it here: URL"

      You will get above error with an URL - Click over that URL - Login in Firebase
      and this will prompt to Create an Index which is required in Firebase 
      to apply queries to Database Collection.
    */
    return this.firestore.collection(
      'Employees', ref => {
        // declare var `query` of type either `CollectionReference` or `Query`
        let query: firebase.firestore.CollectionReference | firebase.firestore.Query = ref;

        // 👇 the below conditions will be applied to query
        // 👇 only when params have value in given `employee` object.

        // where condition to match employee with given phone
        if (employee.phone) {
          query = query.where('phone', '==', employee.phone);
        }
        // where condition to match employee with given salary
        if (employee.salary) {
          // #2. Get items by range '>=' operators, this query
          // will return the employee whose salary is
          // greater than or equal to given `employee.salary`
          query = query.where('salary', '>=', employee.salary);
        }
        // where condition to match employee with given designation
        if (employee.designation) {
          query = query.where('designation', '==', employee.designation);
        }
        // where condition to match employee with given joinDate
        if (employee.joinDate) {
          // covert date string to date object
          employee.joinDateTimestamp = new Date(employee.joinDate);
          query = query.where('joinDateTimestamp', '==', employee.joinDateTimestamp);
        }

        /* #3 also apply query to salary order by `ascending`. */
        // query = query.orderBy('salary', 'asc');

        /* #4 apply query to joinDateTimestamp order by `descending`. */
        // query = query.orderBy('joinDateTimestamp', 'desc');

        /* #5. Apply limit to Query result.
           default order by is ascending (when we not pass second param to orderBy)
           this query will return only 2 employees
        */
        // query = query.orderBy('designation').limit(2);

        /* IMPORTANT: Reason I put this query at last because
         * We can not call Query.startAt() or Query.startAfter()
         * before calling Query.orderBy().
        */

        // where condition to match employee with given name
        if (employee.name) {
          /* look: orderBy and equality '==' cannot apply together.
              that is the reason I comment this equality
          */
          // query = query.where('name', '==', employee.name);

          // #6. Offset by a property, suppose we want employee whose
          // name starts with `An` then apply startAt('An')
          query = query.orderBy('name', 'asc').startAt('An');

          /* similar query `endAt`, `startAfter` and `endBefore`
              can be applied like this:
          */
          // query = query.endAt('An');
          // query = query.startAfter('An');
          // query = query.endBefore('An');
        }

        // finally return query back to the collection second argument.
        return query;
    }).snapshotChanges();
  }

}

This is all👇 we covered in Advance Query in Firebase Firestore:

// Firestore Collection name: 'Employees'

// Order ascending
this.firestore.collection('Employees', ref => ref.orderBy('age'));

// Order descending by numbers or strings
this.firestore.collection('Employees', ref => ref.orderBy('age', 'desc'));
this.firestore.collection('Employees', ref => ref.orderBy('name', 'desc')); // reverse alphabetical

// Limit results
this.firestore.collection('Employees', ref => ref.orderBy('age').limit(5));

// Offset by a property
this.firestore.collection('Employees', ref => ref.orderBy('name').startAt('An'));

// Get items by equality to a property
this.firestore.collection('Employees', ref => ref.where('name', '==', 'Ankit'));

// Get items by range operators
this.firestore.collection('Employees', ref => ref.where('age', '>=', 5));

// Chain equality for multiple properties
this.firestore.collection('Employees', ref => ref.where('age', '==', 5) .where('name', '==', 'Ankit'));

Done! 🤩 It’s that simple to do CRUD operations in Firebase with Firestore.

See you later👋👋


Next, you may cover:

  1. How to add Firebase in PWA or Angular project using AngularFire. (Click here👆)
  2. Add Firebase Authentication to PWA or Angular project using AngularFire. (Click here👆)
  3. Deploy project to Firebase Hosting. (Click here👆)

Feel free to comment down in the comment box… If I missed anything or anything is incorrect or anything does not works for you :)
Stay connected for more articles.