Building web applications with Angular has come a long way since version 6. Back then, developers relied heavily on NgModules, verbose boilerplate, and a less streamlined integration with Firebase. Fast-forward to today, Angular 20 introduces a new era of simplicity and power with standalone components, signals-based reactivity, and modern template syntax like @if
and @for
.
In this tutorial, we’ll modernize the classic Angular + Firebase Firestore CRUD app using the latest Angular 20 features. You’ll learn how to:
-
Set up an Angular 20 project with standalone components (no NgModules).
-
Connect your app to Firebase Firestore using the AngularFire library.
-
Implement full CRUD (Create, Read, Update, Delete) functionality.
-
Use signals and the new template control flow for cleaner, more reactive code.
By the end, you’ll have a fast, maintainable, and future-proof Firestore CRUD application built with the latest Angular practices.
Setup & Project Initialization (Angular 20 + Firebase)
1. Install Node.js and Angular CLI
Ensure you have the latest Node.js LTS version installed (recommended:≥ v20). Then install Angular CLI globally:
npm install -g @angular/cli
Verify the version:
ng version
It should show Angular CLI: 20.x.x.
2. Create a New Angular 20 Project
Generate a new Angular project using the standalone mode flag:
ng new angular20-firestore --standalone
cd angular20-firestore
This will scaffold a new Angular app where the root component (AppComponent
) is standalone.
✅ No AppModule
is generated — everything starts from main.ts
.
3. Set Up Firebase Project
-
Go to Firebase Console.
-
Create a new project (or use an existing one).
-
Enable Firestore Database in Production mode.
-
Copy your project’s Firebase config object (from Project Settings → SDK setup and configuration). It looks like:
export const environment = {
firebase: {
apiKey: "AIzaSyxxxxxx...",
authDomain: "your-app.firebaseapp.com",
projectId: "your-app",
storageBucket: "your-app.appspot.com",
messagingSenderId: "1234567890",
appId: "1:1234567890:web:abcdef123456"
}
};
4. Install Firebase & AngularFire
Add Firebase SDK and AngularFire library:
npm install firebase @angular/fire
5. Configure Firebase in Angular
Open src/app/app.config.ts
and import Firebase providers inside the bootstrapApplication
function:
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
import { getFirestore, provideFirestore } from '@angular/fire/firestore';
import { environment } from '../environments/environment';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideFirebaseApp(() => initializeApp(environment.firebase)),
provideFirestore(() => getFirestore()),
]
};
6. Create src/environments/environment.ts
export const environment = {
production: true,
firebase: {
apiKey: "AIzaSyxxxxxx...",
authDomain: "your-app.firebaseapp.com",
projectId: "your-app",
storageBucket: "your-app.appspot.com",
messagingSenderId: "1234567890",
appId: "1:1234567890:web:abcdef123456"
}
};
This sets up Firebase and Firestore globally in your Angular 20 app.
✅ At this point, you have:
-
A brand-new Angular 20 project with standalone components.
-
Firebase + Firestore is integrated and ready to use.
Creating the Firestore Service (CRUD Operations)
We’ll create a service that interacts with Firestore using the modular Firebase SDK and AngularFire’s helpers.
1. Create the Service File
Generate a new service:
ng generate service services/firestore.service
This will create src/app/services/firestore.service.ts
.
2. Implement Firestore CRUD Methods
Open firestore
.service
.ts
and update it:
import { Injectable } from '@angular/core';
import { collectionData, docData } from '@angular/fire/firestore';
import { collection, doc, addDoc, updateDoc, deleteDoc, Firestore } from 'firebase/firestore';
import { Observable } from 'rxjs';
export interface Board {
id?: string; // Firestore document ID
title: string;
description: string;
author: string;
}
@Injectable({
providedIn: 'root'
})
export class FirestoreService {
constructor(private firestore: Firestore) { }
// Get all boards (Realtime stream)
getBoards(): Observable<Board[]> {
const boardsRef = collection(this.firestore, 'boards');
return collectionData(boardsRef, { idField: 'id' }) as Observable<Board[]>;
}
// Get a single board by ID
getBoard(id: string): Observable<Board> {
const boardDocRef = doc(this.firestore, `boards/${id}`);
return docData(boardDocRef, { idField: 'id' }) as Observable<Board>;
}
// Add a new board
addBoard(board: Board) {
const boardsRef = collection(this.firestore, 'boards');
return addDoc(boardsRef, board);
}
// Update an existing board
updateBoard(board: Board) {
const boardDocRef = doc(this.firestore, `boards/${board.id}`);
return updateDoc(boardDocRef, {
title: board.title,
description: board.description,
author: board.author
});
}
// Delete a board
deleteBoard(id: string) {
const boardDocRef = doc(this.firestore, `boards/${id}`);
return deleteDoc(boardDocRef);
}
}
3. What This Service Does
-
getBoards()
→ Returns a live-updating list of all documents inboards
. -
getBoard(id)
→ Retrieves a single board document. -
addBoard(board)
→ Adds a new board to Firestore. -
updateBoard(board)
→ Updates an existing board by ID. -
deleteBoard(id)
→ Deletes a board by ID.
✅ At this point, you have a reusable Firestore service ready to plug into your standalone components for CRUD functionality.
Creating Standalone CRUD Components (Add, List, Edit)
We’ll build three main components:
-
Board List → display all boards
-
Board Add → form to add a new board
-
Board Edit → form to edit an existing board
1. Generate Components
Run the following commands:
ng generate component components/board-list --standalone --flat
ng generate component components/board-add --standalone --flat
ng generate component components/board-edit --standalone --flat
This will create:
-
src/app/components/board-list.component.ts
-
src/app/components/board-add.component.ts
-
src/app/components/board-edit.component.ts
2. Board List Component
board-list.ts
import { Component, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { FirestoreService, Board } from '../services/firestore.service';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-board-list',
imports: [RouterModule, CommonModule],
templateUrl: './board-list.html',
styleUrl: './board-list.scss'
})
export class BoardList {
firestore = inject(FirestoreService);
boards$: Observable<Board[]> = this.firestore.getBoards();
deleteBoard(id: string) {
this.firestore.deleteBoard(id);
}
}
board-list.html
<div class="container">
<h2>Boards</h2>
<button routerLink="/add" class="btn btn-primary">Add Board</button>
<ul class="list-group mt-3">
<li
*ngFor="let board of boards$ | async"
class="list-group-item d-flex justify-content-between align-items-center"
>
<span>
<strong>{{ board.title }}</strong> - {{ board.description }}
</span>
<span>
<button [routerLink]="['/edit', board.id]" class="btn btn-sm btn-warning me-2">Edit</button>
<button (click)="deleteBoard(board.id!)" class="btn btn-sm btn-danger">Delete</button>
</span>
</li>
</ul>
</div>
3. Board Add Component
board-add.ts
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { FirestoreService, Board } from '../services/firestore.service';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-board-add',
imports: [CommonModule, FormsModule],
templateUrl: './board-add.html',
styleUrl: './board-add.scss'
})
export class BoardAdd {
firestore = inject(FirestoreService);
router = inject(Router);
board: Board = { title: '', description: '', author: '' };
async addBoard() {
await this.firestore.addBoard(this.board);
this.router.navigate(['/']);
}
}
board-add.html
<div class="container">
<h2>Add Board</h2>
<form (ngSubmit)="addBoard()">
<div class="mb-3">
<label class="form-label">Title</label>
<input [(ngModel)]="board.title" name="title" class="form-control" required />
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea
[(ngModel)]="board.description"
name="description"
class="form-control"
required
></textarea>
</div>
<div class="mb-3">
<label class="form-label">Author</label>
<input [(ngModel)]="board.author" name="author" class="form-control" required />
</div>
<button type="submit" class="btn btn-success">Save</button>
</form>
</div>
4. Board Edit Component
board-edit.ts
import { Component, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { switchMap } from 'rxjs';
import { FirestoreService, Board } from '../services/firestore.service';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-board-edit',
imports: [CommonModule, FormsModule],
templateUrl: './board-edit.html',
styleUrl: './board-edit.scss'
})
export class BoardEdit {
firestore = inject(FirestoreService);
route = inject(ActivatedRoute);
router = inject(Router);
board: Board | undefined;
constructor() {
this.route.paramMap
.pipe(switchMap(params => this.firestore.getBoard(params.get('id')!)))
.subscribe(board => this.board = board);
}
async updateBoard() {
if (this.board?.id) {
await this.firestore.updateBoard(this.board);
this.router.navigate(['/']);
}
}
}
board-edit.ts
@if(board) {
<div class="container">
<h2>Edit Board</h2>
<form (ngSubmit)="updateBoard()">
<div class="mb-3">
<label class="form-label">Title</label>
<input [(ngModel)]="board.title" name="title" class="form-control" required />
</div>
<div class="mb-3">
<label class="form-label">Description</label>
<textarea
[(ngModel)]="board.description"
name="description"
class="form-control"
required
></textarea>
</div>
<div class="mb-3">
<label class="form-label">Author</label>
<input [(ngModel)]="board.author" name="author" class="form-control" required />
</div>
<button type="submit" class="btn btn-primary">Update</button>
</form>
</div>
}
✅ With these three components, you now have:
-
List → see all boards and delete them.
-
Add → create a new board.
-
Edit → update an existing board.
Next, we need to configure routing so navigation works between them.
App Routing Setup (Standalone Router Config)
In Angular 20, routing can be defined directly with the provideRouter
API and no AppRoutingModule
is needed. We’ll set up routes for list, add, and edit pages.
1. Update app.routes.ts
Open src/app/app.routes.ts
and update it like this:
import { Routes } from '@angular/router';
import { BoardList } from './components/board-list';
import { BoardAdd } from './components/board-add';
import { BoardEdit } from './components/board-edit';
export const routes: Routes = [
{ path: '', component: BoardList },
{ path: 'add', component: BoardAdd },
{ path: 'edit/:id', component: BoardEdit },
];
2. Add a Router Outlet in app.html
Ensure AppComponent
has a <router-outlet>
so routes can render inside it.
<h1 class="title">Angular 20 Firestore CRUD App</h1>
<nav>
<a routerLink="/">Home</a> |
<a routerLink="/add">Add Item</a>
</nav>
<router-outlet></router-outlet>
Update app.scss
:
nav {
margin: 1rem 0;
}
a {
margin-right: 1rem;
}
✅ At this point:
-
Visiting
/
→ shows the list of items. -
Visiting
/add
→ shows the add form. -
Visiting
/edit/:id
→ shows the edit form.
Styling the App + Testing CRUD Functionality
1. Add Global Styles
Open src/styles.css
and add some simple, responsive CSS for a clean look:
/* You can add global styles to this file, and also import other style files */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 2rem;
background: #f9f9f9;
}
h1.title {
color: #1976d2;
text-align: center;
}
nav {
display: flex;
justify-content: center;
margin-bottom: 1.5rem;
}
nav a {
text-decoration: none;
color: #1976d2;
font-weight: 500;
margin: 0 1rem;
}
nav a:hover {
text-decoration: underline;
}
form {
max-width: 400px;
margin: 2rem auto;
display: flex;
flex-direction: column;
gap: 1rem;
}
input,
button {
padding: 0.6rem;
font-size: 1rem;
}
button {
cursor: pointer;
border: none;
background-color: #1976d2;
color: white;
border-radius: 4px;
transition: background 0.3s;
}
button:hover {
background-color: #125ea7;
}
ul {
list-style: none;
padding: 0;
max-width: 500px;
margin: 2rem auto;
}
li {
background: white;
margin-bottom: 0.5rem;
padding: 1rem;
border-radius: 6px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.actions button {
margin-left: 0.5rem;
}
2. Enhance List View
Update list-items.component.html
template for better UI:
<div class="container">
<h2>Boards</h2>
<button routerLink="/add" class="btn btn-primary">Add Board</button>
<ul class="list-group mt-3">
<li
*ngFor="let board of boards$ | async"
class="list-group-item d-flex justify-content-between align-items-center"
>
<span>
<strong>{{ board.title }}</strong> - {{ board.description }}
</span>
<span>
<button [routerLink]="['/edit', board.id]" class="btn btn-sm btn-warning me-2">Edit</button>
<button (click)="deleteBoard(board.id!)" class="btn btn-sm btn-danger">Delete</button>
</span>
</li>
</ul>
</div>
3. Testing CRUD Functionality
Now let’s test each step in the browser:
-
Start the app
ng serve
Then go to http://localhost:4200
.
-
Test Add Item
-
Click “Add Item” in the navbar.
-
Enter a name and submit.
-
You should be redirected to the list and see your new item appear.
-
-
Test Edit Item
-
Click Edit next to an item.
-
Change the name, save, and check the updated list.
-
-
Test Delete Item
-
Click Delete on an item.
-
It should be removed from Firestore instantly.
-
-
Firestore Console Check
-
Open the Firebase Console → Firestore Database.
-
You should see your items updating live as you add, edit, and delete.
-
✅ At this point, you’ve built a fully functional Angular 20 + Firebase Firestore CRUD web app with modern standalone APIs.
Conclusion
In this tutorial, you learned how to build a modern Angular 20 CRUD application with Firebase Firestore using standalone components and the latest Angular features. We covered:
-
Setting up an Angular 20 project and integrating Firebase.
-
Creating a Firestore service with full CRUD operations.
-
Building standalone components for listing, adding, and editing items.
-
Configuring standalone routing with
provideRouter
. -
Styling the app with Angular Material and testing the functionality end-to-end.
The result is a cleaner, more maintainable, and future-proof app compared to older Angular versions that relied heavily on NgModules. With Angular 20, reactivity via signals, simplified template control flow, and a standalone architecture make development smoother than ever.
Next Steps
Here are a few directions you can take this project further:
-
Authentication & Authorization
-
Secure your Firestore data by integrating Firebase Authentication (Google, GitHub, or Email/Password login).
-
-
Deploy to Firebase Hosting
-
Take your app live with just a few commands using
ng deploy
and Firebase Hosting.
-
-
Advanced Firestore Features
-
Add real-time listeners, pagination, or query-based filtering to handle larger datasets.
-
-
Error Handling & Toast Notifications
-
Improve UX by showing snackbars for errors and successful CRUD actions.
-
-
PWA (Progressive Web App) Support
-
Enable offline persistence with Firestore and turn this into a PWA for a native-like experience.
-
You can find the full working source code on our GitHub.
If you don’t want to waste your time designing your front-end or your budget to spend by hiring a web designer, then Angular Templates is the best place to go. So, speed up your front-end web development with premium Angular templates. Choose your template for your front-end project here.
That's just the basics. If you need more deep learning about Angular, you can take the following cheap course:
- Angular Crash Course - Learn Angular And Google Firebase
- Real-time Communication using Socket. IO 3.x and Angular 11.x
- Angular Progressive Web Apps (PWA) MasterClass & FREE E-Book
- Enterprise-Scale Web Apps with Angular
- Angular Forms In Depth (Angular 20)
- Microservicios Spring Boot y Angular MySql Postgres
-
Angular Developer Interview Questions Practice Test Quiz
Thanks!