cyberangles guide

Building a Real-Time Application with Angular and Firebase

In today’s fast-paced digital world, users expect applications to update instantly—whether it’s a chat app showing new messages, a collaborative tool syncing edits, or a dashboard reflecting live data. Building such real-time features traditionally required complex backend setups with WebSockets or long polling. However, with **Angular** (a powerful frontend framework) and **Firebase** (a backend-as-a-service platform), you can create robust real-time applications with minimal effort. Angular provides a structured, component-based architecture for building dynamic UIs, while Firebase offers a suite of tools (like Firestore, Authentication, and Hosting) that handle backend complexity, including real-time data synchronization. Together, they form a stack that lets you focus on user experience rather than server management. In this tutorial, we’ll build a real-time todo list application with Angular and Firebase. You’ll learn to set up Firebase, integrate it with Angular, implement real-time data updates, add user authentication, and deploy the app—all in under an hour!

Table of Contents

  1. Prerequisites
  2. Setting Up Your Angular Project
  3. Setting Up Firebase
  4. Integrating Firebase with Angular
  5. Building the Real-Time Feature: A Todo List
  6. Adding User Authentication
  7. Deploying the Application to Firebase Hosting
  8. Conclusion
  9. 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

  1. Go to the Firebase Console and click Add project.
  2. Enter a project name (e.g., angular-realtime-todo) and click Continue.
  3. Disable Google Analytics (optional) and click Create project.

Step 2: Register Your App with Firebase

Once the project is created:

  1. Click the Web icon (</>) to register a web app.
  2. Enter an app nickname (e.g., todo-app) and click Register app.
  3. 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:

  1. In the Firebase Console, go to Firestore Database (under Build > Firestore Database).
  2. Click Create database.
  3. Select Start in test mode (for development; we’ll secure it later) and click Next.
  4. Choose a region (e.g., us-central1) and click Enable.

To add user login (we’ll use Google Sign-In later):

  1. Go to Authentication > Sign-in method.
  2. Click Add new provider and select Google.
  3. 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 { }  

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