I am implementing a modern state management pattern in Angular (version 16+) where a dedicated service acts as a state facade, integrating traditional RxJS asynchronous operations with the new Signals primitive.
The goals of this pattern are:
The service (e.g.,
DataService) holds the reactive state internally using privatesignals(e.g.,#loading,#items).The service performs the
HttpClientcalls, which return RxJS Observables.The service explicitly uses
.subscribe()to bridge the Observable stream result into the Signals state.Components consume the state via public
readonlySignals, abstracting away all RxJS complexity.
Context of the Problematic Implementation
Since the service is typically configured as providedIn: 'root', it has an application-wide lifespan, meaning its lifecycle is not inherently tied to a single component's destruction.
The following simplified code demonstrates the pattern but highlights the critical issue:
TypeScript
import { HttpClient } from '@angular/common/http';
import { Injectable, signal, inject } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class DataService {
private readonly http = inject(HttpClient);
// Reactive state
readonly #items = signal();
readonly items = this.#items.asReadonly();
// Method that triggers the request and subscription
fetchData(): void {
// CRITICAL ISSUE: Explicit.subscribe() in a root-provided service
// necessitates manual cleanup to prevent potential memory leaks.
this.http.get('/api/data').subscribe({
next: (data: any) => this.#items.set(data),
error: (err) => console.error(err)
// *Cleanup logic is currently missing*
});
}
}
Specific Technical Question
When using a service with providedIn: 'root' to bridge HttpClient Observables into Signals via an explicit .subscribe(), the subscription needs to be safely destroyed to prevent memory leaks if the service were to be destroyed (or if the application navigates away, although the service instance persists).
What is the most robust, idiomatic, and modern Angular technique (e.g., utilizing inject(DestroyRef) or a managed RxJS operator like takeUntil within the service itself) to ensure this explicit HTTP subscription is automatically and safely cleaned up?