In this tutorial, I will show you how to build Angular 16 Firebase example: CRUD App with Realtime Database that uses AngularFireDatabase
service.
Related Posts:
– Angular 16 Firestore CRUD example
– Angular 16 Firebase Storage: File Upload example
– Angular 16 CRUD example with Rest API
– Angular 16 Form Validation example
Contents
- Angular 16 Firebase CRUD example Overview
- AngularFireDatabase service
- Technology
- Setup the Firebase Project
- Setup Angular 16 Project
- Project Structure
- Import Bootstrap
- Integrate Firebase into Angular 16
- Define Model Class
- Create Data Service
- Component for creating Object
- Component for List of Objects
- Component for Object details
- Define Routes for App Routing Module
- Add Navbar and Router View to Angular 16 Firebase App
- Run & Check
- Conclusion
- Further Reading
- Source Code
Angular 16 Firebase CRUD example Overview
We’re gonna build an Angular 16 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:
Firebase Realtime Database right after the Operation:
If you want to add Form validation, please visit:
– Retrieve all Tutorials with details when clicking on a Tutorial:
– Change status to Published/Pending using Publish/UnPublish button:
– Update the Tutorial details with Update button:
– Delete the Tutorial using Delete button:
– Delete all Tutorials with Remove All button:
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 16
- firebase 9
- @angular/fire 7
- 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 16
Setup Angular 16 Project
Let’s open cmd and use Angular CLI to create a new Angular Project as following command:
ng new angular-16-firebase-crud
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS
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
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 itemtutorials-list
contains list of items, parent oftutorial-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
/ styles.css
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 open src/styles.css and add following line:
@import "~bootstrap/dist/css/bootstrap.css";
Integrate Firebase into Angular 16
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 'src/app/models/tutorial.model';
import { TutorialService } from 'src/app/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">
<div *ngIf="!submitted">
<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>
<div *ngIf="submitted">
<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 'src/app/services/tutorial.service';
import { Tutorial } from 'src/app/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">
<li
class="list-group-item"
*ngFor="let tutorial of tutorials; let i = index"
[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">
<div *ngIf="currentTutorial">
<app-tutorial-details
(refreshList)="refreshList()"
[tutorial]="currentTutorial"
></app-tutorial-details>
</div>
<div *ngIf="!currentTutorial">
<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 'src/app/models/tutorial.model';
import { TutorialService } from 'src/app/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
<div *ngIf="currentTutorial" 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>
<button
class="badge badge-primary mr-2"
*ngIf="currentTutorial.published"
(click)="updatePublished(false)"
>
UnPublish
</button>
<button
*ngIf="!currentTutorial.published"
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>
<div *ngIf="!currentTutorial">
<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
foradd-tutorial
component/tutorials
fortutorials-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 { }
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 16 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 16 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 16 Firestore CRUD example
Or File upload:
Angular 16 Firebase Storage example
You can also find how to create Angular HTTP Client for working with Restful API in:
Angular 16 CRUD example with Rest API
Happy learning, see you again!
Further Reading
- Angular Template Syntax
- Angular Router Guide
- https://www.npmjs.com/package/@angular/fire
- Firebase Web Get Started
Fullstack CRUD Application:
– Angular 16 + Node Express + MySQL example
– Angular 16 + Node Express + PostgreSQL example
– Angular 16 + Node Express + MongoDB example
– Angular 16 + Spring Boot + H2 example
– Angular 16 + Spring Boot + MySQL example
– Angular 16 + Spring Boot + PostgreSQL example
– Angular 16 + 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.