Angular 17 Firebase CRUD example

In this tutorial, I will show you how to build Angular 17 Firebase example: CRUD App with Realtime Database that uses AngularFireDatabase service.

Related Posts:
Angular 17 Firestore CRUD example
Angular 17 Firebase Storage: File Upload example
Angular 17 CRUD example with Rest API
Angular 17 Form Validation example


Angular 17 Firebase CRUD example Overview

We’re gonna build an Angular 17 Firebase Application example using @angular/fire library in which:

  • Each Tutorial has key, title, description, published status.
  • We can make CRUD operations: create, retrieve, update, delete Tutorials.

Here are the screenshots:

– Create a new Tutorial:

angular-17-firebase-example-crud-create

Firebase Realtime Database right after the Operation:

angular-17-firebase-example-crud-realtime-database

If you want to add Form validation, please visit:

– Retrieve all Tutorials with details when clicking on a Tutorial:

angular-17-firebase-example-crud-retrieve

– Change status to Published/Pending using Publish/UnPublish button:

angular-17-firebase-example-crud-tutorial-update-status

– Update the Tutorial details with Update button:

angular-17-firebase-example-crud-tutorial-update

– Delete the Tutorial using Delete button:

angular-17-firebase-example-crud-delete

– Delete all Tutorials with Remove All button:

angular-17-firebase-example-crud-delete-all

AngularFireDatabase service

@angular/fire provides AngularFireDatabase service that allows us to work with the Realtime Database. It’s an efficient, low-latency solution for apps that require synced states across clients in realtime.

import { AngularFireDatabase} from '@angular/fire/compat/database';

export class TutorialService {
  constructor(private db: AngularFireDatabase) { }
}

AngularFireDatabase for Object

The AngularFireObject is a service for manipulating and streaming object data which is created via AngularFireDatabase service.

– Create an object binding/ Retrieve:

tutorial: AngularFireObject<any>;
// db: AngularFireDatabase
this.tutorial = db.object('tutorial');
 
// or
Observable<any> tutorial = db.object('tutorial').valueChanges();

– Create/Update an object:

const tutRef = db.object('tutorial');
 
// set() for destructive updates
tutRef.set({ title: 'zkoder Tutorial'});

– Update an object:

const tutRef= db.object('tutorial');
tutRef.update({ url: 'bezkoder.com/zkoder-tutorial' });

– Delete an object:

const tutRef = db.object('tutorial');
tutRef.remove();

AngularFireDatabase for List

Through the AngularFireDatabase service, we can create AngularFireList service that helps to synchronize data as lists.

– Create a list binding/ Retrieve:
+ Get an Observable of data as a synchronized array of JSON objects without snapshot metadata.

tutorials: Observable<any[]>;
// db: AngularFireDatabase
this.tutorials = db.list('tutorials').valueChanges();

+ Get an Observable of data as a synchronized array of AngularFireAction<DatabaseSnapshot>[] with metadata (the underyling DatabaseReference and snapshot key):

tutorials: Observable<any[]>;
this.tutorials = db.list('tutorials').snapshotChanges();

– Create a List and push a new object:

const tutorialsRef = db.list('tutorials');
tutorialsRef.push({ title: 'zkoder Tutorial', url: 'bezkoder.com/zkoder-tutorial' });

– Update a List:
+ destructive update using set(): delete everything currently in place, then save the new value

const tutorialsRef = db.list('tutorials');
tutorialsRef.set('key', { title: 'zkoder Tut#1', url: 'bezkoder.com/zkoder-tut-1' });

+ non-destructive update using update(): only updates the specified values

const tutorialsRef = db.list('tutorials');
tutorialsRef.update('key', { title: 'zkoder new Tut#1' });

– Delete an object in List:

const tutorialsRef = db.list('tutorials');
tutorialsRef.remove('key');

– Delete entire List:

const tutorialsRef = db.list('tutorials');
tutorialsRef.remove();

Technology

  • Angular 17
  • firebase 10
  • @angular/fire 17
  • rxjs 7
  • bootstrap 4

Setup the Firebase Project

Please visit following tutorial for setting up project on Firebase Console:
How to Integrate Firebase into Angular 17

Setup Angular 17 Project

Let’s open cmd and use Angular CLI to create a new Angular Project as following command:

ng new angular-17-firebase-crud --no-standalone
? Which stylesheet format would you like to use? CSS
? Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? No

We also need to generate some Components and Services:

ng g s services/tutorial

ng g c components/add-tutorial
ng g c components/tutorial-details
ng g c components/tutorials-list

ng g class models/tutorial --type=model

Now you can see that our project directory structure looks like this.

Project Structure

angular-17-firebase-example-crud-project

Let me explain it briefly.

environment.ts configures information to connect with Firebase Project.
models/tutorial.model.ts defines data model class.
services/tutorial.service.ts exports TutorialService that uses @angular/fire‘s AngularFireDatabase to interact with Firebase Database.
– There are 3 components that uses TutorialService:

  • add-tutorial for creating new item
  • tutorials-list contains list of items, parent of tutorial-details
  • tutorial-details shows item details

app-routing.module.ts defines routes for each component.
app.component contains router view and navigation bar.
app.module.ts declares Angular components and imports necessary environment & modules.
index.html / angular.json is for importing Bootstrap.

Import Bootstrap

Open index.html, in <head> tag, import Bootstrap 4 to this project:

<!doctype html>
<html lang="en">
<head>
  ...
  <link rel="stylesheet" href="//unpkg.com/[email protected]/dist/css/bootstrap.min.css" />
</head>
<body>
  <app-root></app-root>
</body>
</html>

Another way is to run the command: npm install [email protected].
Then add following code into angular.json:

"styles": [
    "node_modules/bootstrap/dist/css/bootstrap.min.css",
    "src/styles.css"
],
"scripts": [
    "node_modules/jquery/dist/jquery.slim.min.js",
    "node_modules/popper.js/dist/umd/popper.min.js",
    "node_modules/bootstrap/dist/js/bootstrap.min.js"
]

Integrate Firebase into Angular 17

First run the command: npm install firebase @angular/fire.

Open src/environments/environment.ts, add Firebase configuration that we have saved when Popup window was shown:

export const environment = {
  production: false,
  firebase: {
    apiKey: 'xxx',
    authDomain: 'bezkoder-firebase.firebaseapp.com',
    databaseURL: 'https://bezkoder-firebase.firebaseio.com',
    projectId: 'bezkoder-firebase',
    storageBucket: 'bezkoder-firebase.appspot.com',
    messagingSenderId: 'xxx',
    appId: 'xxx'
  }
};

Open app.module.ts, import AngularFireModule, AngularFireDatabaseModule and environment:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AngularFireModule } from '@angular/fire/compat';
import { AngularFireDatabaseModule } from '@angular/fire/compat/database';
import { environment } from '../environments/environment';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AddTutorialComponent } from './components/add-tutorial/add-tutorial.component';
import { TutorialDetailsComponent } from './components/tutorial-details/tutorial-details.component';
import { TutorialsListComponent } from './components/tutorials-list/tutorials-list.component';

@NgModule({
  declarations: [
    AppComponent,
    AddTutorialComponent,
    TutorialDetailsComponent,
    TutorialsListComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    AngularFireModule.initializeApp(environment.firebase),
    AngularFireDatabaseModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Define Model Class

Let’s create Tutorial class with 4 fields: key, title, description, published.

models/tutorial.model.ts

export class Tutorial {
  key?: string | null;
  title?: string;
  description?: string;
  published?: boolean;
}

Create Data Service

This service will use AngularFireDatabase and AngularFireList to interact with Firebase Realtime Database. It contains necessary functions for CRUD operations.

services/tutorial.service.ts

import { Injectable } from '@angular/core';
import { AngularFireDatabase, AngularFireList } from '@angular/fire/compat/database';
import { Tutorial } from '../models/tutorial.model';

@Injectable({
  providedIn: 'root'
})
export class TutorialService {
  private dbPath = '/tutorials';

  tutorialsRef: AngularFireList<Tutorial>;

  constructor(private db: AngularFireDatabase) {
    this.tutorialsRef = db.list(this.dbPath);
  }

  getAll(): AngularFireList<Tutorial> {
    return this.tutorialsRef;
  }

  create(tutorial: Tutorial): any {
    return this.tutorialsRef.push(tutorial);
  }

  update(key: string, value: any): Promise<void> {
    return this.tutorialsRef.update(key, value);
  }

  delete(key: string): Promise<void> {
    return this.tutorialsRef.remove(key);
  }

  deleteAll(): Promise<void> {
    return this.tutorialsRef.remove();
  }
}

Component for creating Object

his component has a Form to submit new Tutorial with 2 fields: title & description. It calls TutorialService.create() method.

components/add-tutorial/add-tutorial.component.ts

import { Component } from '@angular/core';
import { Tutorial } from '../../models/tutorial.model';
import { TutorialService } from '../../services/tutorial.service';

@Component({
  selector: 'app-add-tutorial',
  templateUrl: './add-tutorial.component.html',
  styleUrls: ['./add-tutorial.component.css']
})
export class AddTutorialComponent {

  tutorial: Tutorial = new Tutorial();
  submitted = false;

  constructor(private tutorialService: TutorialService) { }

  saveTutorial(): void {
    this.tutorialService.create(this.tutorial).then(() => {
      console.log('Created new item successfully!');
      this.submitted = true;
    });
  }

  newTutorial(): void {
    this.submitted = false;
    this.tutorial = new Tutorial();
  }
}

components/add-tutorial/add-tutorial.component.html

<div class="submit-form">
  @if (!submitted) {
  <div>
    <div class="form-group">
      <label for="title">Title</label>
      <input
        type="text"
        class="form-control"
        id="title"
        required
        [(ngModel)]="tutorial.title"
        name="title"
      />
    </div>

    <div class="form-group">
      <label for="description">Description</label>
      <input
        class="form-control"
        id="description"
        required
        [(ngModel)]="tutorial.description"
        name="description"
      />
    </div>

    <button (click)="saveTutorial()" class="btn btn-success">Submit</button>
  </div>
  } @else {
  <div>
    <h4>You submitted successfully!</h4>
    <button class="btn btn-success" (click)="newTutorial()">Add</button>
  </div>
  }
</div>

components/add-tutorial/add-tutorial.component.css

.submit-form {
  max-width: 300px;
  margin: auto;
}

Component for List of Objects

This component calls TutorialService methods:

  • getAll()
  • deleteAll()

components/tutorials-list/tutorials-list.component.ts

import { Component, OnInit } from '@angular/core';
import { TutorialService } from '../../services/tutorial.service';
import { Tutorial } from '../../models/tutorial.model';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-tutorials-list',
  templateUrl: './tutorials-list.component.html',
  styleUrls: ['./tutorials-list.component.css']
})
export class TutorialsListComponent implements OnInit {
  tutorials?: Tutorial[];
  currentTutorial?: Tutorial;
  currentIndex = -1;
  title = '';

  constructor(private tutorialService: TutorialService) { }

  ngOnInit(): void {
    this.retrieveTutorials();
  }

  refreshList(): void {
    this.currentTutorial = undefined;
    this.currentIndex = -1;
    this.retrieveTutorials();
  }

  retrieveTutorials(): void {
    this.tutorialService.getAll().snapshotChanges().pipe(
      map(changes =>
        changes.map(c =>
          ({ key: c.payload.key, ...c.payload.val() })
        )
      )
    ).subscribe(data => {
      this.tutorials = data;
    });
  }

  setActiveTutorial(tutorial: Tutorial, index: number): void {
    this.currentTutorial = tutorial;
    this.currentIndex = index;
  }

  removeAllTutorials(): void {
    this.tutorialService.deleteAll()
      .then(() => this.refreshList())
      .catch(err => console.log(err));
  }
}

In the code above, to get the key of each item, we use snapshotChanges() with RxJS map() operator. This key is unique and important for update operation.

We also have refreshList() function for every time delete operation is done.

components/tutorials-list/tutorials-list.component.html

<div class="list row">
  <div class="col-md-6">
    <h4>Tutorials List</h4>
    <ul class="list-group">
      @for (tutorial of tutorials; track tutorial.key; let i = $index) {
      <li
        class="list-group-item"
        [class.active]="i == currentIndex"
        (click)="setActiveTutorial(tutorial, i)"
      >
        {{ tutorial.title }}
      </li>
      }
    </ul>

    <button class="m-3 btn btn-sm btn-danger" (click)="removeAllTutorials()">
      Remove All
    </button>
  </div>
  <div class="col-md-6">
    @if (currentTutorial) {
    <div>
      <app-tutorial-details
        (refreshList)="refreshList()"
        [tutorial]="currentTutorial"
      ></app-tutorial-details>
    </div>
    } @else {
    <div>
      <br />
      <p>Please click on a Tutorial...</p>
    </div>
    }
  </div>
</div>

You can see that when we click on any item, setActiveTutorial() function will be invoked to change current active Tutorial, which data is passed to tutorial-details component.

components/tutorials-list/tutorials-list.component.css

.list {
  text-align: left;
  max-width: 750px;
  margin: auto;
}

Component for Object details

This component is the child of tutorial-list. It bind tutorial data and emit refreshList event to the parent.

For getting update, delete the Tutorial, we’re gonna use two TutorialService methods:

  • update()
  • delete()

components/tutorial-details/tutorial-details.component.ts

import { Component, OnInit, Input, OnChanges, Output, EventEmitter } from '@angular/core';
import { Tutorial } from '../../models/tutorial.model';
import { TutorialService } from '../../services/tutorial.service';

@Component({
  selector: 'app-tutorial-details',
  templateUrl: './tutorial-details.component.html',
  styleUrls: ['./tutorial-details.component.css']
})
export class TutorialDetailsComponent implements OnInit {
  @Input() tutorial?: Tutorial;
  @Output() refreshList: EventEmitter<any> = new EventEmitter();
  currentTutorial: Tutorial = {
    title: '',
    description: '',
    published: false
  };
  message = '';

  constructor(private tutorialService: TutorialService) { }

  ngOnInit(): void {
    this.message = '';
  }

  ngOnChanges(): void {
    this.message = '';
    this.currentTutorial = { ...this.tutorial };
  }

  updatePublished(status: boolean): void {
    if (this.currentTutorial.key) {
      this.tutorialService.update(this.currentTutorial.key, { published: status })
      .then(() => {
        this.currentTutorial.published = status;
        this.message = 'The status was updated successfully!';
      })
      .catch(err => console.log(err));
    }
  }

  updateTutorial(): void {
    const data = {
      title: this.currentTutorial.title,
      description: this.currentTutorial.description
    };

    if (this.currentTutorial.key) {
      this.tutorialService.update(this.currentTutorial.key, data)
        .then(() => this.message = 'The tutorial was updated successfully!')
        .catch(err => console.log(err));
    }
  }

  deleteTutorial(): void {
    if (this.currentTutorial.key) {
      this.tutorialService.delete(this.currentTutorial.key)
        .then(() => {
          this.refreshList.emit();
          this.message = 'The tutorial was updated successfully!';
        })
        .catch(err => console.log(err));
    }
  }
}

components/tutorial-details/tutorial-details.component.html

@if (currentTutorial) {
<div class="edit-form">
  <h4>Tutorial</h4>
  <form>
    <div class="form-group">
      <label for="title">Title</label>
      <input
        type="text"
        class="form-control"
        id="title"
        [(ngModel)]="currentTutorial.title"
        name="title"
      />
    </div>
    <div class="form-group">
      <label for="description">Description</label>
      <input
        type="text"
        class="form-control"
        id="description"
        [(ngModel)]="currentTutorial.description"
        name="description"
      />
    </div>

    <div class="form-group">
      <label><strong>Status:</strong></label>
      {{ currentTutorial.published ? "Published" : "Pending" }}
    </div>
  </form>

  @if (currentTutorial.published) {
  <button class="badge badge-primary mr-2" (click)="updatePublished(false)">
    UnPublish
  </button>
  } @else {
  <button class="badge badge-primary mr-2" (click)="updatePublished(true)">
    Publish
  </button>
  }

  <button class="badge badge-danger mr-2" (click)="deleteTutorial()">
    Delete
  </button>

  <button type="submit" class="badge badge-success" (click)="updateTutorial()">
    Update
  </button>
  <p>{{ message }}</p>
</div>
} @else {
<div>
  <br />
  <p>Cannot access this Tutorial...</p>
</div>
}

components/tutorial-details/tutorial-details.component.css

.edit-form {
  max-width: 300px;
  margin: auto;
}

Define Routes for App Routing Module

There are 2 main routes:

  • /add for add-tutorial component
  • /tutorials for tutorials-list component

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { TutorialsListComponent } from './components/tutorials-list/tutorials-list.component';
import { AddTutorialComponent } from './components/add-tutorial/add-tutorial.component';

const routes: Routes = [
  { path: '', redirectTo: 'tutorials', pathMatch: 'full' },
  { path: 'tutorials', component: TutorialsListComponent },
  { path: 'add', component: AddTutorialComponent }
];

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

Add Navbar and Router View to Angular 17 Firebase App

Let’s open src/app.component.html, this App component is the root container for our application, it will contain a nav element.

<div>
  <nav class="navbar navbar-expand navbar-dark bg-dark">
    <a href="#" class="navbar-brand">bezKoder</a>
    <div class="navbar-nav mr-auto">
      <li class="nav-item">
        <a routerLink="tutorials" class="nav-link">Tutorials</a>
      </li>
      <li class="nav-item">
        <a routerLink="add" class="nav-link">Add</a>
      </li>
    </div>
  </nav>

  <div class="container mt-3">
    <h3>{{ title }}</h3>
    <router-outlet></router-outlet>
  </div>
</div>

Run & Check

You can run this Angular 17 Firebase CRUD example with command: ng serve.
Open browser with url: http://localhost:4200/ and check the result.

Conclusion

Today we’ve built an Angular 17 Firebase CRUD Application successfully working with Realtime Database using AngularFireDatabase from @angular/fire library. Now we can display, modify, delete object and list at ease.

If you want to use Firestore instead, please visit:
Angular 17 Firestore CRUD example

Or File upload:
Angular 17 Firebase Storage example

You can also find how to create Angular HTTP Client for working with Restful API in:
Angular 17 CRUD example with Rest API

Happy learning, see you again!

Further Reading

Fullstack CRUD Application:
Angular + Node Express + MySQL example
Angular + Node Express + PostgreSQL example
Angular + Node Express + MongoDB example
Angular 17 + Spring Boot + H2 example
Angular 17 + Spring Boot + MySQL example
Angular 17 + Spring Boot + PostgreSQL example
Angular 17 + Spring Boot + MongoDB example
Angular + Django example
Angular + Django + MySQL example
Angular + Django + PostgreSQL example
Angular + Django + MongoDB example

Source Code

You can find the complete source code for this tutorial on Github.