Skip to content

Caso d'uso del Subject.

I Subject RxJS possono essere utilizzati in diversi scenari pratici. Questa sezione presenta casi d'uso pratici per la famiglia Subject (Subject, BehaviorSubject, ReplaySubject e AsyncSubject) e descrive le situazioni in cui ciascuno di essi è più adatto.

Modelli di gestione dello stato

Implementazione di un semplice negozio.

Usare BehaviorSubject per implementare un semplice negozio che può contenere, aggiornare e sottoscrivere lo stato dell'applicazione.

ts
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs';

interface AppState {
  user: { name: string; role: string } | null;
  theme: 'light' | 'dark';
  notifications: string[];
}

// Stato iniziale
const initialState: AppState = {
  user: null,
  theme: 'light',
  notifications: []
};

class Store {
  // BehaviorSubjectGestione dello stato con
  private state$ = new BehaviorSubject<AppState>(initialState);
  
  // Metodi per leggere lo stato
  getState() {
    return this.state$.getValue();
  }
  
  // Recupera la proprietà specificata comeObservableRecupera come
  select<K extends keyof AppState>(key: K) {
    return this.state$.pipe(
      map(state => state[key])
    );
  }
  
  // Aggiornamento dello stato
  setState(newState: Partial<AppState>) {
    this.state$.next({
      ...this.getState(),
      ...newState
    });
  }
  
  // Recupera lo stato comeObservablePubblica come
  get state() {
    return this.state$.asObservable();
  }
}

// Esempio di utilizzo
const store = new Store();

// Monitoraggio dello stato
store.select('user').subscribe(user => {
  console.log('Cambiamenti di stato dell'utente:', user?.name, user?.role);
});

// Monitoraggio delle modifiche al tema
store.select('theme').subscribe(theme => {
  console.log('Modifiche al tema:', theme);
  document.body.className = theme; // UIRiflesso in
});

// Aggiornamento dello stato
store.setState({ user: { name: 'Taro Yamada', role: 'admin' } });
store.setState({ theme: 'dark' });

Risultato dell'esecuzione

sh
Cambiamenti di stato dell'utente: undefined undefined
Modifiche al tema: light
Cambiamenti di stato dell'utente: Taro Yamada admin
Modifiche al tema: light
Cambiamenti di stato dell'utente: Taro Yamada admin
Modifiche al tema: dark

Questo pattern è utile per le applicazioni di piccole dimensioni o quando non si utilizzano librerie di gestione degli stati di grandi dimensioni, come NgRx o Redux.

Comunicazione da componente a componente

Implementazione del bus degli eventi.

Implementare un bus di eventi basato su Subject, che può gestire diversi tipi di dati per diversi tipi di notifica, per la comunicazione tra componenti.

ts
import { Subject } from 'rxjs';
import { filter, map } from 'rxjs';

type EventPayloadMap = {
  USER_LOGIN: { username: string; timestamp: number };
  DATA_UPDATED: any;
  NOTIFICATION: string;
};

// Definizione del tipo di evento
type EventType = keyof EventPayloadMap;

interface AppEvent<K extends EventType> {
  type: K;
  payload: EventPayloadMap[K];
}

// Servizio bus eventi
class EventBusService {
  private eventSubject = new Subject<AppEvent<unknown>>();

  emit<K extends EventType>(type: K, payload: EventPayloadMap[K]): void {
    this.eventSubject.next({ type, payload });
  }

  // Sottoscrizione di eventi di un tipo specifico
  on<K extends EventType>(type: K) {
    return this.eventSubject.pipe(
      filter((event): event is AppEvent<K> => event.type === type),
      map((event) => event.payload)
    );
  }
}
// Esempio di utilizzo) Comunicazione da componente a componente
const eventBus = new EventBusService();

// Componente Header (visualizza le notifiche)
eventBus.on('NOTIFICATION').subscribe((message) => {
  console.log('Intestazione: Visualizza le notifiche:', message);
});

// Componente utente (controlla lo stato di accesso)
eventBus.on('USER_LOGIN').subscribe((user) => {
  console.log('Componente utente: Rilevamento dell'accesso:', user.username);
});

// Componente di configurazione (controlla gli aggiornamenti dei dati)
eventBus.on('DATA_UPDATED').subscribe((data) => {
  console.log('Componente di configurazione: Aggiornamento dati:', data);
});

// Evento emesso
eventBus.emit('USER_LOGIN', { username: 'user123', timestamp: Date.now() });
eventBus.emit('NOTIFICATION', 'C'è un nuovo messaggio');

Risultati dell'esecuzione

sh
Componente utente: Rilevamento dell'accesso: user123
Intestazione: Visualizza le notifiche: C'è un nuovo messaggio

Il pattern Event Bus è un modo eccellente per ottenere una comunicazione intercomponente non accoppiata. È particolarmente adatto per la comunicazione tra componenti distanti tra loro nella gerarchia.

CAUTION

💡 Nelle applicazioni reali, la mancata unsubscribe (unsubscribe()) può portare a perdite di memoria. Si consideri anche il processo di disiscrizione tramite l'uso di takeUntil()` o simili.

Caching dei dati API

Condivisione e cache dei risultati delle richieste

Usare AsyncSubject per condividere e mettere in cache i dati che vengono emessi una sola volta, come le richieste HTTP.

ts
import { Observable, AsyncSubject, of, throwError } from 'rxjs';
import { tap, catchError, delay } from 'rxjs';

class ApiCacheService {
  private cache = new Map<string, AsyncSubject<any>>();

  fetchData<T>(url: string): Observable<T> {
    // Lo restituisce se esiste nella cache
    if (this.cache.has(url)) {
      console.log(`Recupera i dati dalla cache: ${url}`);
      return this.cache.get(url)!.asObservable() as Observable<T>;
    }

    // Se non è presente nella cache, crea una nuova richiesta
    console.log(`APIEseguire la richiesta: ${url}`);
    const subject = new AsyncSubject<T>();
    this.cache.set(url, subject);

    // APISimula la richiesta
    this.makeRequest<T>(url)
      .pipe(
        tap((data) => {
          subject.next(data);
          subject.complete();
        }),
        catchError((error: unknown) => {
          // Cancella dalla cache in caso di errore
          this.cache.delete(url);
          subject.error(error);
          return throwError(() => error);
        })
      )
      .subscribe();

    return subject.asObservable();
  }

  // Simulare l'effettivaAPIElaborazione della richiesta
  private makeRequest<T>(url: string): Observable<T> {
    // Nelle applicazioni realifetcheHTTPUtilizzare il client
    return of({
      data: 'Dati di esempio',
      timestamp: Date.now(),
    } as unknown as T).pipe(
      tap(() => console.log('APIRicezione della risposta')),
      // Simulare ritardi casuali
      delay(Math.random() * 1000 + 500)
    );
  }

  // Cancellare la cache
  clearCache(url?: string): void {
    if (url) {
      this.cache.delete(url);
    } else {
      this.cache.clear();
    }
    console.log('Cache cancellata');
  }
}

// Esempio di utilizzo
const apiCache = new ApiCacheService();

// Più componenti richiedono lo stesso datoAPIRichiesta di dati
apiCache.fetchData('/api/products').subscribe((data) => {
  console.log('Componente1: Dati ricevuti', data);
});

// Poco dopo anche un altro componente richiede gli stessi dati (recuperati dalla cache)
setTimeout(() => {
  apiCache.fetchData('/api/products').subscribe((data) => {
    console.log('Componente2: Dati ricevuti', data);
  });
}, 1000);

// Richiesta di nuovo dopo aver cancellato la cache
setTimeout(() => {
  apiCache.clearCache();
  apiCache.fetchData('/api/products').subscribe((data) => {
    console.log('Componente3: Dati ricevuti (dopo aver cancellato la cache)', data);
  });
}, 2000);

Risultati dell'esecuzione

sh
APIEseguire la richiesta: /api/products
APIRicezione della risposta
Componente1: Dati ricevuti {data: 'Dati di esempio', timestamp: 1745405703582}
Recupera i dati dalla cache: /api/products
Componente2: Dati ricevuti {data: 'Dati di esempio', timestamp: 1745405703582}
Cache cancellata
APIEseguire la richiesta: /api/products
APIRicezione della risposta
Componente3: Dati ricevuti (dopo aver cancellato la cache) {data: 'Dati di esempio', timestamp: 1745405705585}

Questo schema con AsyncSubject è ideale per le richieste API in cui è importante solo l'ultimo valore al completamento. Inoltre, impedisce la duplicazione della stessa richiesta.

ts
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs';

interface AppState {
  user: { name: string; role: string } | null;
  theme: 'light' | 'dark';
  notifications: string[];
}

// Stato iniziale
const initialState: AppState = {
  user: null,
  theme: 'light',
  notifications: []
};

class Store {
  // BehaviorSubjectGestione dello stato con
  private state$ = new BehaviorSubject<AppState>(initialState);
  
  // Metodi per leggere lo stato
  getState() {
    return this.state$.getValue();
  }
  
  // Recupera la proprietà specificata comeObservableRecupera come
  select<K extends keyof AppState>(key: K) {
    return this.state$.pipe(
      map(state => state[key])
    );
  }
  
  // Aggiornamento dello stato
  setState(newState: Partial<AppState>) {
    this.state$.next({
      ...this.getState(),
      ...newState
    });
  }
  
  // Recupera lo stato comeObservablePubblica come
  get state() {
    return this.state$.asObservable();
  }
}

// Esempio di utilizzo
const store = new Store();

// Monitoraggio dello stato
store.select('user').subscribe(user => {
  console.log('Cambiamenti di stato dell'utente:', user?.name, user?.role);
});

// Monitoraggio delle modifiche al tema
store.select('theme').subscribe(theme => {
  console.log('Modifiche al tema:', theme);
  document.body.className = theme; // UIRiflesso in
});

// Aggiornamento dello stato
store.setState({ user: { name: 'Taro Yamada', role: 'admin' } });
store.setState({ theme: 'dark' });

Si noti che AsyncSubject notificherà solo un errore', non un valore, se viene chiamato error()`.

Gestione dei moduli.

Usare BehaviorSubject per gestire il valore corrente e lo stato di convalida di un modulo reattivo.

Legame bidirezionale dei valori dei form.

ts
import { BehaviorSubject } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs';

interface UserForm {
  name: string;
  email: string;
  age: number;
}

class ReactiveForm {
  // Con valori inizialiBehaviorSubject
  private formSubject = new BehaviorSubject<UserForm>({
    name: '',
    email: '',
    age: 0
  });
  
  // Per la pubblicazioneObservable
  formValues$ = this.formSubject.asObservable();
  
  // Risultato della convalida
  private validSubject = new BehaviorSubject<boolean>(false);
  valid$ = this.validSubject.asObservable();
  
  constructor() {
    // Convalida eseguita quando si modificano i valori
    this.formValues$.pipe(
      debounceTime(300),
      distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr))
    ).subscribe(form => {
      this.validateForm(form);
    });
  }
  
  // Aggiornamento dei valori dei campi
  updateField<K extends keyof UserForm>(field: K, value: UserForm[K]) {
    const currentForm = this.formSubject.getValue();
    this.formSubject.next({
      ...currentForm,
      [field]: value
    });
  }
  
  // Recupero del modulo
  getForm(): UserForm {
    return this.formSubject.getValue();
  }
  
  // Convalida
  private validateForm(form: UserForm) {
    const isValid = 
      form.name.length > 0 && 
      form.email.includes('@') &&
      form.age > 0;
      
    this.validSubject.next(isValid);
  }
  
  // Invio del modulo
  submit() {
    if (this.validSubject.getValue()) {
      console.log('Invio del modulo:', this.getForm());
      // APIRichieste ecc.
    } else {
      console.error('Il modulo non è valido');
    }
  }
}

// Esempio di utilizzo
const form = new ReactiveForm();

// Monitoraggio dei valori del modulo
form.formValues$.subscribe(values => {
  console.log('Modifiche del valore del modulo:', values);
  // UIprocesso di aggiornamento, ecc.
});

// Monitoraggio dello stato di convalida
form.valid$.subscribe(isValid => {
  console.log('Validità del modulo:', isValid);
  // Abilitazione/disabilitazione del pulsante di invio/Disabilitazione del pulsante di invio, ecc.
});

// Simulazione dell'input dell'utente
form.updateField('name', 'Taro Yamada');
form.updateField('email', 'yamada@example.com');
form.updateField('age', 30);

// Invio del modulo
form.submit();

Risultato dell'esecuzione

sh
Modifiche del valore del modulo: {name: '', email: '', age: 0}
Validità del modulo: false
Modifiche del valore del modulo: {name: 'Taro Yamada', email: '', age: 0}
Modifiche del valore del modulo: {name: 'Taro Yamada', email: 'yamada@example.com', age: 0}
Modifiche del valore del modulo: {name: 'Taro Yamada', email: 'yamada@example.com', age: 30}
Il modulo non è valido
submit @ 
(anonimo) @ Analizzare l'errore
Validità del modulo: true

Questo pattern è particolarmente utile per le implementazioni reattive dei form, in quanto BehaviorSubject mantiene sempre il valore corrente, rendendolo ideale per la gestione dello stato del form.

Registrazione e cronologia

Usare ReplaySubject per creare un meccanismo di gestione dei log, in grado di conservare e visualizzare la cronologia delle operazioni passate.

Gestione della cronologia delle operazioni.

ts
import { Observable, ReplaySubject } from 'rxjs';
import { tap } from 'rxjs';

interface LogEntry {
  action: string;
  timestamp: number;
  data?: any;
}

class ActivityLogger {
  // Ultimo10Conserva un registro degli errori più recenti
  private logSubject = new ReplaySubject<LogEntry>(10);
  logs$ = this.logSubject.asObservable();
  
  // Aggiungere una voce di registro
  log(action: string, data?: any) {
    const entry: LogEntry = {
      action,
      timestamp: Date.now(),
      data
    };
    
    this.logSubject.next(entry);
    console.log(`Registrato: ${action}`, data);
  }
  
  // Avvolge un altroObservableAvvolge e registra un registro
  wrapWithLogging<T>(source$: Observable<T>, actionName: string): Observable<T> {
    return source$.pipe(
      tap(data => this.log(actionName, data))
    );
  }
}

// Esempio di utilizzo
const logger = new ActivityLogger();

// Monitorare i registri (ad es.UI(ad esempio, per visualizzarli)
logger.logs$.subscribe(log => {
  const time = new Date(log.timestamp).toLocaleTimeString();
  console.log(`[${time}] ${log.action}`);
});

// Registra vari々Registra operazioni come
logger.log('Avvio dell'applicazione');
logger.log('Accesso dell'utente', { userId: 'user123' });

// Poco dopo, si avvia la sottoscrizione di un nuovo componente, che include i registri passati.
setTimeout(() => {
  console.log('--- Il visualizzatore della cronologia include i registri passati. ---');
  logger.logs$.subscribe(log => {
    const time = new Date(log.timestamp).toLocaleTimeString();
    console.log(`La storia: [${time}] ${log.action}`);
  });
  
  // Ulteriori registri aggiunti
  logger.log('Aggiornamento dati', { itemId: 456 });
}, 1000);

Risultato dell'esecuzione

ts
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs';

interface AppState {
  user: { name: string; role: string } | null;
  theme: 'light' | 'dark';
  notifications: string[];
}

// Stato iniziale
const initialState: AppState = {
  user: null,
  theme: 'light',
  notifications: []
};

class Store {
  // BehaviorSubjectGestione dello stato con
  private state$ = new BehaviorSubject<AppState>(initialState);
  
  // Metodi per leggere lo stato
  getState() {
    return this.state$.getValue();
  }
  
  // Recupera la proprietà specificata comeObservableRecupera come
  select<K extends keyof AppState>(key: K) {
    return this.state$.pipe(
      map(state => state[key])
    );
  }
  
  // Aggiornamento dello stato
  setState(newState: Partial<AppState>) {
    this.state$.next({
      ...this.getState(),
      ...newState
    });
  }
  
  // Recupera lo stato comeObservablePubblica come
  get state() {
    return this.state$.asObservable();
  }
}

// Esempio di utilizzo
const store = new Store();

// Monitoraggio dello stato
store.select('user').subscribe(user => {
  console.log('Cambiamenti di stato dell'utente:', user?.name, user?.role);
});

// Monitoraggio delle modifiche al tema
store.select('theme').subscribe(theme => {
  console.log('Modifiche al tema:', theme);
  document.body.className = theme; // UIRiflesso in
});

// Aggiornamento dello stato
store.setState({ user: { name: 'Taro Yamada', role: 'admin' } });
store.setState({ theme: 'dark' });

ReplaySubject può essere usato per fornire ai nuovi sottoscrittori le voci di registro passate, ideale per la gestione della cronologia. È utile per tracciare le operazioni dell'utente e raccogliere informazioni di debug.

IMPORTANT

⚠️ Se non si specifica una dimensione del buffer per ReplaySubject, tutti i valori continueranno a essere conservati in memoria, quindi occorre prestare attenzione a grandi quantità di dati o ad applicazioni di lunga durata.

Gestione dell'elaborazione asincrona

Utilizzate Subject e BehaviourSubject per gestire l'avanzamento e lo stato attivo di più attività in tempo reale.

Gestire l'avanzamento di task di lunga durata.

ts
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs';

interface AppState {
  user: { name: string; role: string } | null;
  theme: 'light' | 'dark';
  notifications: string[];
}

// Stato iniziale
const initialState: AppState = {
  user: null,
  theme: 'light',
  notifications: []
};

class Store {
  // BehaviorSubjectGestione dello stato con
  private state$ = new BehaviorSubject<AppState>(initialState);
  
  // Metodi per leggere lo stato
  getState() {
    return this.state$.getValue();
  }
  
  // Recupera la proprietà specificata comeObservableRecupera come
  select<K extends keyof AppState>(key: K) {
    return this.state$.pipe(
      map(state => state[key])
    );
  }
  
  // Aggiornamento dello stato
  setState(newState: Partial<AppState>) {
    this.state$.next({
      ...this.getState(),
      ...newState
    });
  }
  
  // Recupera lo stato comeObservablePubblica come
  get state() {
    return this.state$.asObservable();
  }
}

// Esempio di utilizzo
const store = new Store();

// Monitoraggio dello stato
store.select('user').subscribe(user => {
  console.log('Cambiamenti di stato dell'utente:', user?.name, user?.role);
});

// Monitoraggio delle modifiche al tema
store.select('theme').subscribe(theme => {
  console.log('Modifiche al tema:', theme);
  document.body.className = theme; // UIRiflesso in
});

// Aggiornamento dello stato
store.setState({ user: { name: 'Taro Yamada', role: 'admin' } });
store.setState({ theme: 'dark' });

Risultati dell'esecuzione

Questo schema fornisce una notifica in tempo reale dell'avanzamento di un'attività in corso da tempo, utilizzando Subject. È adatto per visualizzare l'avanzamento del caricamento dei file, dell'elaborazione dei dati e delle operazioni in background.

Aggiornamenti in tempo reale

Gestire lo stato della connessione WebSocket, i messaggi in arrivo e il controllo della riconnessione utilizzando più Subject.

Gestione dei flussi WebSocket.

ts
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs';

interface AppState {
  user: { name: string; role: string } | null;
  theme: 'light' | 'dark';
  notifications: string[];
}

// Stato iniziale
const initialState: AppState = {
  user: null,
  theme: 'light',
  notifications: []
};

class Store {
  // BehaviorSubjectGestione dello stato con
  private state$ = new BehaviorSubject<AppState>(initialState);
  
  // Metodi per leggere lo stato
  getState() {
    return this.state$.getValue();
  }
  
  // Recupera la proprietà specificata comeObservableRecupera come
  select<K extends keyof AppState>(key: K) {
    return this.state$.pipe(
      map(state => state[key])
    );
  }
  
  // Aggiornamento dello stato
  setState(newState: Partial<AppState>) {
    this.state$.next({
      ...this.getState(),
      ...newState
    });
  }
  
  // Recupera lo stato comeObservablePubblica come
  get state() {
    return this.state$.asObservable();
  }
}

// Esempio di utilizzo
const store = new Store();

// Monitoraggio dello stato
store.select('user').subscribe(user => {
  console.log('Cambiamenti di stato dell'utente:', user?.name, user?.role);
});

// Monitoraggio delle modifiche al tema
store.select('theme').subscribe(theme => {
  console.log('Modifiche al tema:', theme);
  document.body.className = theme; // UIRiflesso in
});

// Aggiornamento dello stato
store.setState({ user: { name: 'Taro Yamada', role: 'admin' } });
store.setState({ theme: 'dark' });

Risultati dell'esecuzione

Questo pattern di gestione WebSocket è ideale per le applicazioni che richiedono una comunicazione in tempo reale; utilizza Subject per gestire lo stato della connessione e il flusso dei messaggi, che possono essere condivisi da più componenti.

##Linee guida per la scelta di un Subject.

ts
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs';

interface AppState {
  user: { name: string; role: string } | null;
  theme: 'light' | 'dark';
  notifications: string[];
}

// Stato iniziale
const initialState: AppState = {
  user: null,
  theme: 'light',
  notifications: []
};

class Store {
  // BehaviorSubjectGestione dello stato con
  private state$ = new BehaviorSubject<AppState>(initialState);
  
  // Metodi per leggere lo stato
  getState() {
    return this.state$.getValue();
  }
  
  // Recupera la proprietà specificata comeObservableRecupera come
  select<K extends keyof AppState>(key: K) {
    return this.state$.pipe(
      map(state => state[key])
    );
  }
  
  // Aggiornamento dello stato
  setState(newState: Partial<AppState>) {
    this.state$.next({
      ...this.getState(),
      ...newState
    });
  }
  
  // Recupera lo stato comeObservablePubblica come
  get state() {
    return this.state$.asObservable();
  }
}

// Esempio di utilizzo
const store = new Store();

// Monitoraggio dello stato
store.select('user').subscribe(user => {
  console.log('Cambiamenti di stato dell'utente:', user?.name, user?.role);
});

// Monitoraggio delle modifiche al tema
store.select('theme').subscribe(theme => {
  console.log('Modifiche al tema:', theme);
  document.body.className = theme; // UIRiflesso in
});

// Aggiornamento dello stato
store.setState({ user: { name: 'Taro Yamada', role: 'admin' } });
store.setState({ theme: 'dark' });

💡 È convenzione comune di RxJS terminare i nomi delle variabili con $ per indicare che sono Observable.

La famiglia Subject di RxJS è uno strumento potente per una varietà di casi d'uso, tra cui.

  • BehaviorSubject**: gestione degli stati, gestione dei moduli, visualizzazione dei valori correnti.
  • Subject**: notifica di eventi, comunicazione tra componenti.
  • ReplaySubject**: gestione della cronologia, registri delle operazioni, partecipazione ritardata dei componenti.
  • AsyncSubject**: caching delle risposte API, condivisione dei risultati dei calcoli.

Combinando opportunamente questi pattern, si possono costruire applicazioni reattive e manutenibili. In particolare, bisogna fare attenzione a non dimenticare di disiscriversi al momento giusto, per evitare perdite di memoria.

Pubblicato sotto licenza CC-BY-4.0.