Table of Contents
- Prerequisites
- Setting Up Your Angular Project
- Setting Up Firebase
- Integrating Firebase with Angular
- Building the Real-Time Feature: A Todo List
- Adding User Authentication
- Deploying the Application to Firebase Hosting
- Conclusion
- References
Prerequisites
Before we start, ensure you have the following tools installed:
- Node.js (v14+ recommended) and npm (v6+): To run Angular CLI and manage dependencies.
- Angular CLI: Install via
npm install -g @angular/cli. - Firebase Account: Sign up for free at firebase.google.com.
- Firebase CLI (optional, for deployment): Install via
npm install -g firebase-tools(we’ll use this later).
Setting Up Your Angular Project
First, let’s create a new Angular project. Open your terminal and run:
ng new angular-firebase-realtime-todo
When prompted:
- Would you like to add Angular routing? Select
Yes. - Which stylesheet format would you like to use? Choose
CSS(or your preferred option).
Navigate into the project directory:
cd angular-firebase-realtime-todo
Start the development server to verify the setup:
ng serve --open
You should see the default Angular welcome page at http://localhost:4200.
Setting Up Firebase
Next, we’ll set up a Firebase project and enable the services we need (Firestore for real-time data and Authentication for user login).
Step 1: Create a Firebase Project
- Go to the Firebase Console and click Add project.
- Enter a project name (e.g.,
angular-realtime-todo) and click Continue. - Disable Google Analytics (optional) and click Create project.
Step 2: Register Your App with Firebase
Once the project is created:
- Click the Web icon (</>) to register a web app.
- Enter an app nickname (e.g.,
todo-app) and click Register app. - Copy the Firebase configuration snippet (it looks like this):
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "your-project-id.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project-id.appspot.com",
messagingSenderId: "123456789",
appId: "1:123456789:web:abc123def456"
};
Save this config—we’ll use it to connect Angular to Firebase.
Step 3: Enable Firestore Database
Firestore is Firebase’s NoSQL database that supports real-time updates. To enable it:
- In the Firebase Console, go to Firestore Database (under Build > Firestore Database).
- Click Create database.
- Select Start in test mode (for development; we’ll secure it later) and click Next.
- Choose a region (e.g.,
us-central1) and click Enable.
Step 4: Enable Authentication (Optional but Recommended)
To add user login (we’ll use Google Sign-In later):
- Go to Authentication > Sign-in method.
- Click Add new provider and select Google.
- Enable the provider, enter a project support email, and click Save.
Integrating Firebase with Angular
To connect Angular to Firebase, we’ll use AngularFire—the official Angular library for Firebase.
Step 1: Install AngularFire and Firebase
Run the following command in your Angular project directory:
npm install @angular/fire firebase
Step 2: Add Firebase Configuration to Angular
Create a file src/environments/environment.ts (if it doesn’t exist) and paste your Firebase config:
// src/environments/environment.ts
export const environment = {
production: false,
firebase: {
apiKey: "YOUR_API_KEY",
authDomain: "your-project-id.firebaseapp.com",
projectId: "your-project-id",
storageBucket: "your-project-id.appspot.com",
messagingSenderId: "123456789",
appId: "1:123456789:web:abc123def456"
}
};
Step 3: Initialize AngularFire in app.module.ts
Open src/app/app.module.ts and import the required AngularFire modules. We’ll use AngularFireModule (core), AngularFirestoreModule (for Firestore), and AngularFireAuthModule (for authentication):
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AngularFireModule } from '@angular/fire/compat';
import { AngularFirestoreModule } from '@angular/fire/compat/firestore';
import { AngularFireAuthModule } from '@angular/fire/compat/auth';
import { environment } from '../environments/environment';
import { FormsModule } from '@angular/forms'; // For form handling
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
AppRoutingModule,
AngularFireModule.initializeApp(environment.firebase), // Initialize Firebase
AngularFirestoreModule, // Enable Firestore
AngularFireAuthModule, // Enable Authentication
FormsModule // Enable template-driven forms
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Building the Real-Time Feature: A Todo List
We’ll build a real-time todo list where users can add and view todos, with updates reflected instantly across all connected devices.
5.1 Creating the Todo Interface
First, define a Todo interface to represent our data. Create a file src/app/models/todo.ts:
// src/app/models/todo.ts
export interface Todo {
id?: string; // Optional, as Firestore auto-generates IDs
title: string;
createdAt: Date;
userId?: string; // To link todos to users (added later)
}
5.2 Building the Todo Service
Create a service to handle Firestore operations (fetching, adding, and deleting todos). Run:
ng generate service services/todo
Open src/app/services/todo.service.ts and add the following code:
// src/app/services/todo.service.ts
import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/compat/firestore';
import { Todo } from '../models/todo';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class TodoService {
// Reference to the "todos" collection in Firestore
private todosCollection: AngularFirestoreCollection<Todo>;
constructor(private firestore: AngularFirestore) {
// Initialize the collection (auto-created if it doesn't exist)
this.todosCollection = this.firestore.collection<Todo>('todos');
}
// Get all todos (real-time updates)
getTodos(): Observable<Todo[]> {
// snapshotChanges() returns changes to the collection (add, remove, update)
return this.todosCollection.snapshotChanges().pipe(
map(actions =>
actions.map(a => {
const data = a.payload.doc.data() as Todo; // Get todo data
const id = a.payload.doc.id; // Get Firestore document ID
return { id, ...data }; // Return todo with ID
})
)
);
}
// Add a new todo
addTodo(title: string): void {
const todo: Todo = {
title,
createdAt: new Date()
};
this.todosCollection.add(todo); // Adds to Firestore (auto-generates ID)
}
}
5.3 Creating the Todo Component
Now, create a component to display and add todos. Run:
ng generate component components/todo-list
Update the Todo List Component Class
Open src/app/components/todo-list/todo-list.component.ts and inject the TodoService:
// src/app/components/todo-list/todo-list.component.ts
import { Component } from '@angular/core';
import { TodoService } from '../../services/todo.service';
import { Todo } from '../../models/todo';
import { Observable } from 'rxjs';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent {
todos$: Observable<Todo[]>; // Observable for real-time todos
newTodoTitle = ''; // Bound to the input form
constructor(private todoService: TodoService) {
this.todos$ = this.todoService.getTodos(); // Fetch todos on component load
}
// Add a new todo when the form is submitted
addTodo(): void {
if (this.newTodoTitle.trim()) {
this.todoService.addTodo(this.newTodoTitle);
this.newTodoTitle = ''; // Reset input
}
}
}
Update the Todo List Template
Open src/app/components/todo-list/todo-list.component.html and add a form to input todos and a list to display them:
<!-- src/app/components/todo-list/todo-list.component.html -->
<div class="container">
<h2>Real-Time Todo List</h2>
<!-- Add Todo Form -->
<form (ngSubmit)="addTodo()">
<input
type="text"
[(ngModel)]="newTodoTitle"
name="newTodoTitle"
placeholder="Enter a new todo..."
required
>
<button type="submit">Add</button>
</form>
<!-- Todo List -->
<ul>
<li *ngFor="let todo of todos$ | async">
{{ todo.title }}
<small>(Added: {{ todo.createdAt | date:'short' }})</small>
</li>
</ul>
</div>
Add Basic Styling (Optional)
Open src/app/components/todo-list/todo-list.component.css to style the component:
/* src/app/components/todo-list/todo-list.component.css */
.container {
max-width: 600px;
margin: 2rem auto;
padding: 0 1rem;
}
form {
margin-bottom: 2rem;
display: flex;
gap: 0.5rem;
}
input {
flex: 1;
padding: 0.5rem;
font-size: 1rem;
}
button {
padding: 0.5rem 1rem;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
ul {
list-style: none;
padding: 0;
}
li {
padding: 0.75rem;
margin: 0.5rem 0;
background: #f5f5f5;
border-radius: 4px;
display: flex;
justify-content: space-between;
}
small {
color: #666;
font-size: 0.8rem;
}
Update Routing
Open src/app/app-routing.module.ts to set the todo list as the default route:
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { TodoListComponent } from './components/todo-list/todo-list.component';
const routes: Routes = [
{ path: '', component: TodoListComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Test the Real-Time Feature
Run ng serve --open and go to http://localhost:4200. Add a todo, and it will appear in the list instantly. Open the app in another browser tab—adding a todo in one tab will automatically update the other tab! This is Firestore’s real-time synchronization in action.
Adding User Authentication
To make the app more practical, let’s add Google Sign-In so users can only see their own todos.
6.1 Enabling Google Authentication in Firebase
We already enabled Google Auth earlier (in Step 4 of Setting Up Firebase). If not, repeat that step.
6.2 Implementing Google Sign-In in Angular
Create an Auth Service
Run:
ng generate service services/auth
Open src/app/services/auth.service.ts and add authentication logic:
// src/app/services/auth.service.ts
import { Injectable } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/compat/auth';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AuthService {
user$: Observable<any>; // Observable for auth state
constructor(private afAuth: AngularFireAuth) {
this.user$ = this.afAuth.authState; // Track user login state
}
// Google Sign-In
signInWithGoogle(): Promise<any> {
return this.afAuth.signInWithPopup(new (window as any).firebase.auth.GoogleAuthProvider());
}
// Sign Out
signOut(): Promise<any> {
return this.afAuth.signOut();
}
}
Create a Login Component
Run:
ng generate component components/login
Update the login component class (login.component.ts):
// src/app/components/login/login.component.ts
import { Component } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
constructor(private authService: AuthService, private router: Router) { }
login(): void {
this.authService.signInWithGoogle().then(() => {
this.router.navigate(['/']); // Redirect to todo list after login
});
}
}
Update the Login Template
Open login.component.html:
<!-- src/app/components/login/login.component.html -->
<div class="login-container">
<h2>Sign In to Your Todo List</h2>
<button (click)="login()">Sign In with Google</button>
</div>
6.3 Protecting Routes with Angular Guards
We’ll use an Angular guard to ensure only authenticated users can access the todo list.
Generate a Guard
Run:
ng generate guard guards/auth
Select CanActivate when prompted.
Update the Auth Guard
Open src/app/guards/auth.guard.ts:
// src/app/guards/auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) { }
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
return this.authService.user$.pipe(
map(user => {
if (user) return true; // Allow access if user is logged in
this.router.navigate(['/login']); // Redirect to login if not
return false;
}),
catchError(() => {
this.router.navigate(['/login']);
return of(false);
})
);
}
}
Update Routing
Modify app-routing.module.ts to protect the todo list route and add a login route:
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { TodoListComponent } from './components/todo-list/todo-list.component';
import { LoginComponent } from './components/login/login.component';
import { AuthGuard } from './guards/auth.guard';
const routes: Routes = [
{ path: '', component: TodoListComponent, canActivate: [AuthGuard] }, // Protected
{ path: 'login', component: LoginComponent },
{ path: '**', redirectTo: 'login' } // Redirect unknown routes to login
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Update the Todo Service to Link Todos to Users
Modify todo.service.ts to store the user’s ID with each todo and fetch only their todos:
// src/app/services/todo.service.ts (updated)
import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/compat/firestore';
import { Todo } from '../models/todo';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AuthService } from './auth.service';
import { take } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class TodoService {
private todosCollection: AngularFirestoreCollection<Todo>;
constructor(
private firestore: AngularFirestore,
private authService: AuthService
) {
// Get current user and initialize collection with their todos
this.authService.user$.pipe(take(1)).subscribe(user => {
if (user) {
this.todosCollection = this.firestore.collection<Todo>('todos', ref =>
ref.where('userId', '==', user.uid) // Only fetch todos for this user
);
}
});
}
getTodos(): Observable<Todo[]> {
return this.todosCollection.snapshotChanges().pipe(
map(actions =>
actions.map(a => {
const data = a.payload.doc.data() as Todo;
const id = a.payload.doc.id;
return { id, ...data };
})
)
);
}
addTodo(title: string): void {
this.authService.user$.pipe(take(1)).subscribe(user => {
if (user) {
const todo: Todo = {
title,
createdAt: new Date(),
userId: user.uid // Link todo to user
};
this.todosCollection.add(todo);
}
});
}
}
Deploying the Application to Firebase Hosting
Firebase Hosting makes it easy to deploy Angular apps with a single command.
Step 1: Install the Firebase CLI
If you haven’t already, install the Firebase CLI:
npm install -g firebase-tools
Step 2: Initialize Firebase Hosting
Run:
firebase login
Authenticate with your Google account. Then initialize hosting:
firebase init
- Select Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys.
- Choose your Firebase project (e.g.,
angular-realtime-todo). - What do you want to use as your public directory? Enter
dist/angular-firebase-realtime-todo(the Angular build output folder). - Configure as a single-page app? Select
Yes.
Step 3: Build and Deploy
Build your Angular app for production:
ng build --configuration production
Deploy to Firebase Hosting:
firebase deploy
Firebase will output a URL (e.g., https://your-project-id.web.app) where your app is live!
Conclusion
In this tutorial, you built a real-time todo list application using Angular and Firebase. You learned to:
- Set up Angular and Firebase projects.
- Integrate Firebase with Angular using AngularFire.
- Implement real-time data synchronization with Firestore.
- Add Google authentication and protect routes.
- Deploy the app to Firebase Hosting.
Firebase offers many more features to explore, such as Cloud Functions (serverless backend logic), Cloud Storage (file uploads), and Crashlytics (error tracking). Combined with Angular’s flexibility, the possibilities are endless!
References
- Angular Documentation
- Firebase Documentation
- AngularFire Documentation
- Firestore Security Rules (to secure your database in production)
- Example GitHub Repository (replace with your repo URL)