Leveraging Server-Sent Events (SSE) with Angular and Node.js

Introduction

Server-Sent Events (SSE) is a powerful technology that enables servers to push real-time updates to web clients over HTTP. When combined with Angular on the client side and Node.js on the server side, SSE offers a seamless and efficient way to build dynamic, data-driven web applications. In this article, we'll explore how to implement SSE in an Angular application with a Node.js backend.

Understanding Server-Sent Events (SSE)

Server-Sent Events (SSE) is a standard mechanism for sending real-time updates from a server to a web client over HTTP. Unlike traditional HTTP requests, SSE establishes a persistent connection between the client and the server, allowing the server to send data to the client as events occur without the need for the client to repeatedly poll the server for updates.

Now, let's explore how to implement Server-Sent Events (SSE) using Angular + Node.js in a step-by-step manner.

Step 1. Setup Node.js Server

  • Install Node.js: If you haven't already. You can download it from the Node.js official website.
  • Create a new directory for your project and navigate into it using your terminal or command prompt.
  • Initialize a new Node.js project by running the following command:
    npm init -y
  • Install Express.js: a web application framework for Node.js, by running:
    npm install express
  • Create a new file named `server.js` in your project directory. This file will contain the code for your Node.js server. Project directory

Step 2. Write the server-side code for SSE

  • Imports
    import express from "express";
    import http from "http";
    import cors from "cors";

    Here, we import the necessary modules: express for creating the server, http for creating an HTTP server instance, and cors for enabling Cross-Origin Resource Sharing.

  • Server Setup

    const app = express();
    const server = http.createServer(app);
    app.use(cors());

    `express()` creates an Express application instance.
    `http.createServer(app)` creates an HTTP server instance using the Express application.
    `app.use(cors())` enables CORS to allow cross-origin requests.

  • Route Definition

    app.get("/curr-count", (req, res) => {})
  • Setting Headers
     res.setHeader('Content-Type', 'text/event-stream');
        res.setHeader('Cache-Control', 'no-cache');
        res.setHeader('Connection', 'keep-alive');

    `'Content-Type': 'text/event-stream'` specifies that the response will be an event stream.
    `'Cache-Control'`: 'no-cache' disables caching.
    `'Connection'`: 'keep-alive' keeps the connection open for further events.

  • Data Streaming

     let value = 0;
        setInterval(() => {
            res.write(`data: ${JSON.stringify({count: value++})}\n\n`);
        },2000)

    `value` is initialized to 0.
    `setInterval()` is used to send events every 2 seconds.
    `res.write()` writes data to the response stream. The data is formatted as an SSE event with a JSON payload containing the current count incremented each time.

    Note. Make sure that the server is sending events in the correct format. Each SSE event should start with data followed by the payload and end with.

  • Server Start

    server.listen(3000, () => {
        console.log("server started at port 3000");
    })
  • Final Output
    import express from "express";
    import http from "http";
    import cors from "cors";
    
    const app = express();
    const server = http.createServer(app);
    app.use(cors());
    
    app.get("/curr-count", (req, res) => {
    
        res.setHeader('Content-Type', 'text/event-stream');
        res.setHeader('Cache-Control', 'no-cache');
        res.setHeader('Connection', 'keep-alive');
    
        let value = 0;
    
        setInterval(() => {
            res.write(`data: ${JSON.stringify({count: value++})}\n\n`);
        },2000)
    })
    
    server.listen(3000, () => {
        console.log("server started at port 3000");
    })

Step 3. Setup Angular

  • Install Angular CLI: if you haven't already. You can install it globally using npm with the following command:
    npm install -g @angular/cli
  • Create a new Angular project by running
    ng new <your project name>
  • Navigate into your Angular project directory
    cd <your project name>
  • Create a new Angular service: to handle SSE communication. You can create a service named `event.ts` using the code provided in the "Implementing SSE in Angular" section.
     ng g s service/event

Step 4. Write code for Angular

         Inside the `events.ts` service file

  • Property for EventSource Instance
    sse: EventSource
  • getEventData Method
    getEventData(){
      return new Observable(obs=>{
        this.sse = new EventSource('http://localhost:3000/curr-count');
    
        this.sse.onerror = (error) => {
          console.log(error);
        }
    
        this.sse.onmessage = (message) => {
          obs.next(JSON.parse(message.data).count);
        }
      })
    }

    1. This method `getEventData()`returns an observable that will emit data received from the server.
    2. Inside the observable constructor, we establish a connection to the server using EventSource. The URL 'http://localhost:3000/curr-count' is where the server is expected to send Server-Sent Events.
    3. We handle errors that may occur during the connection using the `onerror` event listener.
    4. When a message is received from the server (onmessage event), we parse the message data (assuming it's JSON) and extract the count value. Then, we emit this count value  obs.next() to notify subscribers of the observable.

  •  Final Output 
    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs';
    
    @Injectable({
      providedIn: 'root'
    })
    export class EventService {
    
      sse: EventSource
    
      getEventData(){
        return new Observable(obs=>{
          this.sse = new EventSource('http://localhost:3000/curr-count');
    
          this.sse.onerror = (error) => {
            console.log(error);
          }
      
          this.sse.onmessage = (message) => {
                obs.next(JSON.parse(message.data).count);
          }
        })
      }
    }
    

     

Step 5. Main component

  • Import Dependency Injection
    sse = inject(EventService);
    cdf = inject(ChangeDetectorRef);
  • Create Property
    value = 0;
  • Start Event Method
    start_event() {
      this.sse.getEventData().subscribe({
        next: (data: any) => {
          this.value = data;
          this.cdf.detectChanges();
        }
      });
    }

    1. This method start_event() is called to start receiving Server-Sent Events.
    2. Inside the method, we call `getEventData()` from `EventService` to establish a connection to the server and subscribe to SSE events.
    3. When a new event is received (next), we update the value property with the received data and manually trigger change detection using this.cdf.detectChanges() to ensure. Angular updates the view with the new value.

  • Final Output

    import { ChangeDetectorRef, Component, inject, OnInit } from '@angular/core';
    import { RouterOutlet } from '@angular/router';
    import { EventService } from './service/event.service';
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [RouterOutlet],
      templateUrl: './app.component.html',
      styleUrl: './app.component.css'
    })
    export class AppComponent{
    
      sse = inject(EventService);
      cdf = inject(ChangeDetectorRef)
      value = 0;
    
      start_event() {
        this.sse.getEventData().subscribe({
          next: (data: any) => {
            console.log(data)
            this.value = data;
            this.cdf.detectChanges();
          }
        });
    
      }
      
    }
    

Step 6. Display the value in HTML

<h1>counting From Server-sent events {{value}}</h1>

<button (click)="start_event()">start</button>

Click the 'Start Event' button to increase the value using SSE.

Step 7. Final Browser Output- Visualizing the Result

Final Browser Output: Visualizing the Result

Conclusion

In this article, we've implemented a real-time counter using Server-Sent Events (SSE) in a Node.js server. SSE provides a simple and efficient way to push real-time updates from the server to the client without the need for continuous polling. This example can serve as a foundation for building more sophisticated real-time features in your web applications.