← Retour au blog
AngularSSELLMstreamingreal-timeperformanceRxJS

Avec une approche orientée subject pour plus de contrôle : le service expose un Subject qui reçoit les chunks, et un observable que le composant souscrit. Cela décuple la flexibilité : vous pouvez accumuler les chunks dans un signal, les afficher progressivement avec un délai, ou les traiter avant d'afficher. Voici le pattern recommandé :

Streaming LLM responses via Server-Sent Events (SSE) has become the standard in modern applications. Unlike traditional HTTP calls where you wait for the complete response, SSE allows the server to push data progressively to the client, character by character. For users, this creates a seamless experience: they watch the response being written in real-time, just like ChatGPT or Claude. Implementing this pattern in Angular requires solid understanding of Observables, HTTP interception, and performance considerations. This article walks you through building a robust, scalable solution that handles edge cases and scales to production.

Why SSE Over WebSockets or Polling?

SSE offers decisive advantages for streaming LLM responses. Unlike polling (requests every 100ms), SSE establishes a persistent one-way connection: the server pushes data without the client asking. Compared to WebSockets, SSE is simpler to implement server-side (no negotiation), uses standard HTTP (compatible with all proxies), and auto-reconnects on network failure. For chat or content generation, SSE is sufficient since you only need server→client communication. Network and CPU overhead is minimal: a single TCP connection per user, no complex heartbeat logic. Angular's HttpClient provides native SSE support, making integration natural and testable.

Implementing SSE Observable in Angular

The key is using HttpClient with responseType: 'text' and manual event parsing. Create a service that encapsulates streaming logic and returns an event observable. For each event, decode the SSE format (lines starting with data: ), then emit via a Subject. Use finalize() to clean up the connection and retry() with exponential backoff for network resilience. Angular's change detection automatically triggers when you subscribe via the async pipe, updating the template in real-time without manual intervention. Critically, never block Angular's zone with heavy computation in the subscription callback: use runOutsideAngular() if needed.

import { Injectable, NgZone } from '@angular/core';

import { HttpClient } from '@angular/common/http';

import { Observable, Subject, fromEvent } from 'rxjs';

import { retry, finalize, switchMap } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })

export class LlmStreamService {

constructor(private http: HttpClient, private ngZone: NgZone) {}

streamResponse(prompt: string): Observable<string> {

const subject = new Subject<string>();

this.ngZone.runOutsideAngular(() => {

const eventSource = new EventSource( /api/llm/stream?prompt=${encodeURIComponent(prompt)} );

n eventSource.onmessage = (event) => {

this.ngZone.run(() => subject.next(event.data));

};

eventSource.onerror = () => {

eventSource.close();

subject.complete();

};

});

return subject.asObservable();

}

}

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️