Dificultad de la gesti�n de estado
En RxJS, requisitos como "compartir estado entre m�ltiples componentes" o "cachear resultados de API" son muy comunes, pero elegir el m�todo adecuado es dif�cil. Esta p�gina explica patrones pr�cticos para la gesti�n de estado y compartici�n de streams.
Subject vs BehaviorSubject vs ReplaySubject
Tipos y caracter�sticas de Subject
| Subject | Valor inicial | Comportamiento al suscribirse | Casos de uso comunes |
|---|---|---|---|
| Subject | Ninguno | Solo recibe valores posteriores a la suscripci�n | Event bus, sistema de notificaciones |
| BehaviorSubject | Requerido | Recibe el �ltimo valor inmediatamente | Estado actual (estado de login, item seleccionado) |
| ReplaySubject | Ninguno | Recibe los �ltimos N valores | Historial, logs, registro de operaciones |
| AsyncSubject | Ninguno | Solo recibe el valor final al completarse | Resultado as�ncrono �nico (raramente usado) |
Visualizaci�n de las diferencias de comportamiento de cada Subject
El siguiente diagrama muestra qu� valores recibe cada Subject al suscribirse.
Criterios de selecci�n
- Subject: Notificaci�n de eventos (no se necesita el pasado)
- BehaviorSubject: Gesti�n de estado (se necesita el valor actual)
- ReplaySubject: Gesti�n de historial (se necesitan los �ltimos N valores)
Ejemplo pr�ctico 1: Subject (Event bus)
L Mal ejemplo: No se pueden recibir valores anteriores a la suscripci�n
import { Subject } from 'rxjs';
const notifications$ = new Subject<string>();
notifications$.next('Notificaci�n 1'); // Nadie est� suscrito a�n
notifications$.subscribe(msg => {
console.log('Recibido:', msg);
});
notifications$.next('Notificaci�n 2');
notifications$.next('Notificaci�n 3');
// Salida:
// Recibido: Notificaci�n 2
// Recibido: Notificaci�n 3
// ('Notificaci�n 1' no se recibe)Buen ejemplo: Usar como event bus (solo procesar eventos despu�s de la suscripci�n)
import { filter, map, Subject } from 'rxjs';
class EventBus {
private events$ = new Subject<{ type: string; payload: any }>();
emit(type: string, payload: any) {
this.events$.next({ type, payload });
}
on(type: string) {
return this.events$.pipe(
filter(event => event.type === type),
map(event => event.payload)
);
}
}
const bus = new EventBus();
// Iniciar suscripci�n
bus.on('userLogin').subscribe(user => {
console.log('Login:', user);
});
// Emitir evento
bus.emit('userLogin', { id: 1, name: 'Alice' }); // Se recibe
// Login: {id: 1, name: 'Alice'}Cu�ndo usar Subject
- Arquitectura basada en eventos: Comunicaci�n desacoplada entre componentes
- Sistema de notificaciones: Entrega de notificaciones en tiempo real
- Cuando no se necesitan valores pasados: Solo procesar eventos despu�s de la suscripci�n
Ejemplo pr�ctico 2: BehaviorSubject (Gesti�n de estado)
L Mal ejemplo: Con Subject no se conoce el estado actual
import { Subject } from 'rxjs';
const isLoggedIn$ = new Subject<boolean>();
// Usuario inicia sesi�n
isLoggedIn$.next(true);
// Componente suscrito posteriormente
isLoggedIn$.subscribe(status => {
console.log('Estado de login:', status); // No se imprime nada
});Buen ejemplo: Obtener estado actual inmediatamente con BehaviorSubject
import { BehaviorSubject } from 'rxjs';
class AuthService {
private isLoggedIn$ = new BehaviorSubject<boolean>(false); // Valor inicial: false
login(username: string, password: string) {
// Proceso de login...
this.isLoggedIn$.next(true);
}
logout() {
this.isLoggedIn$.next(false);
}
// Exponer como solo lectura externamente
get isLoggedIn() {
return this.isLoggedIn$.asObservable();
}
// Obtener valor actual sincr�nicamente (solo en casos especiales)
get currentStatus(): boolean {
return this.isLoggedIn$.value;
}
}
const auth = new AuthService();
auth.login('user', 'pass');
// Aunque se suscriba despu�s, puede obtener el estado actual (true) inmediatamente
auth.isLoggedIn.subscribe(status => {
console.log('Estado de login:', status); // Estado de login: true
});Cu�ndo usar BehaviorSubject
- Mantener estado actual: Estado de login, item seleccionado, valores de configuraci�n
- Necesitar valor inmediatamente al suscribirse: Cuando se necesita el estado actual para la visualizaci�n inicial de UI
- Monitorear cambios de estado: Actualizar reactivamente cuando cambia el estado
Ejemplo pr�ctico 3: ReplaySubject (Gesti�n de historial)
Buen ejemplo: Reproducir los �ltimos N valores
import { ReplaySubject } from 'rxjs';
class SearchHistoryService {
// Mantener las �ltimas 5 b�squedas
private history$ = new ReplaySubject<string>(5);
addSearch(query: string) {
this.history$.next(query);
}
getHistory() {
return this.history$.asObservable();
}
}
const searchHistory = new SearchHistoryService();
// Ejecutar b�squedas
searchHistory.addSearch('TypeScript');
searchHistory.addSearch('RxJS');
searchHistory.addSearch('Angular');
// Aunque se suscriba despu�s, puede obtener las �ltimas 3 b�squedas inmediatamente
searchHistory.getHistory().subscribe(query => {
console.log('Historial de b�squeda:', query);
});
// Salida:
// Historial de b�squeda: TypeScript
// Historial de b�squeda: RxJS
// Historial de b�squeda: AngularCu�ndo usar ReplaySubject
- Historial de operaciones: Historial de b�squeda, edici�n, navegaci�n
- Logs y trazas de auditor�a: Registrar operaciones pasadas
- Soporte para suscripci�n tard�a: Cuando se quiere recibir valores pasados aunque la suscripci�n se retrase
Diferencias entre share y shareReplay
Problema: Ejecuci�n duplicada de Observable Cold
L Mal ejemplo: API llamada m�ltiples veces con m�ltiples suscripciones
import { ajax } from 'rxjs/ajax';
const users$ = ajax.getJSON('/api/users');
// Suscripci�n 1
users$.subscribe(users => {
console.log('Componente A:', users);
});
// Suscripci�n 2
users$.subscribe(users => {
console.log('Componente B:', users);
});
// Problema: API llamada 2 veces
// GET /api/users (1� vez)
// GET /api/users (2� vez)Buen ejemplo: Convertir a Hot con share (compartir ejecuci�n)
import { ajax } from 'rxjs/ajax';
import { share } from 'rxjs';
const users$ = ajax.getJSON('/api/users').pipe(
share() // Compartir ejecuci�n
);
// Suscripci�n 1
users$.subscribe(users => {
console.log('Componente A:', users);
});
// Suscripci�n 2 (si se suscribe inmediatamente)
users$.subscribe(users => {
console.log('Componente B:', users);
});
// API llamada solo 1 vez
// GET /api/users (solo una vez)Trampa de share
share() resetea el stream cuando se cancela la �ltima suscripci�n. Se ejecutar� de nuevo la pr�xima vez que se suscriba.
const data$ = fetchData().pipe(share());
// Suscripci�n 1
const sub1 = data$.subscribe();
// Suscripci�n 2
const sub2 = data$.subscribe();
sub1.unsubscribe();
sub2.unsubscribe(); // Todos cancelados � Reset
// Re-suscripci�n � fetchData() se ejecuta de nuevo
data$.subscribe();shareReplay: Cachear y reutilizar resultados
Buen ejemplo: Cachear con shareReplay
import { ajax } from 'rxjs/ajax';
import { shareReplay } from 'rxjs';
const users$ = ajax.getJSON('/api/users').pipe(
shareReplay({ bufferSize: 1, refCount: true })
// bufferSize: 1 � Cachear el �ltimo valor
// refCount: true � Limpiar cach� cuando se cancelen todas las suscripciones
);
// Suscripci�n 1
users$.subscribe(users => {
console.log('Componente A:', users);
});
// Suscripci�n 2 despu�s de 1 segundo (aunque se suscriba tarde, obtiene desde cach�)
setTimeout(() => {
users$.subscribe(users => {
console.log('Componente B:', users); // Obtiene inmediatamente desde cach�
});
}, 1000);
// API llamada solo 1 vez, resultado cacheadoComparaci�n entre share y shareReplay
| Caracter�stica | share() | shareReplay(1) |
|---|---|---|
| Nueva suscripci�n durante suscripciones activas | Comparte el mismo stream | Comparte el mismo stream |
| Suscripci�n tard�a | Solo recibe nuevos valores | Recibe el �ltimo valor cacheado |
| Despu�s de cancelar todas las suscripciones | Reset del stream | Mantiene cach� (si refCount: false) |
| Memoria | No mantiene | Mantiene cach� |
| Caso de uso | Compartir datos en tiempo real | Cachear resultados de API |
Buen ejemplo: Configuraci�n apropiada de shareReplay
import { shareReplay } from 'rxjs';
// Patr�n 1: Cach� persistente (no recomendado)
const data1$ = fetchData().pipe(
shareReplay({ bufferSize: 1, refCount: false })
// refCount: false � Cuidado con memory leaks
);
// Patr�n 2: Cach� con limpieza autom�tica (recomendado)
const data2$ = fetchData().pipe(
shareReplay({ bufferSize: 1, refCount: true })
// refCount: true � Limpiar cach� cuando se cancelen todas las suscripciones
);
// Patr�n 3: Cach� con TTL (RxJS 7.4+)
const data3$ = fetchData().pipe(
shareReplay({
bufferSize: 1,
refCount: true,
windowTime: 5000 // Descartar cach� despu�s de 5 segundos
})
);Advertencia sobre memory leaks
Usar shareReplay({ refCount: false }) deja la cach� persistente, causando memory leaks. B�sicamente use refCount: true.
Uso pr�ctico de Hot vs Cold
Caracter�sticas de Cold: Ejecuci�n por cada suscripci�n
import { Observable } from 'rxjs';
const cold$ = new Observable<number>(subscriber => {
console.log('=5 Inicio de ejecuci�n');
subscriber.next(Math.random());
subscriber.complete();
});
cold$.subscribe(v => console.log('Suscripci�n 1:', v));
cold$.subscribe(v => console.log('Suscripci�n 2:', v));
// Salida:
// =5 Inicio de ejecuci�n
// Suscripci�n 1: 0.123
// =5 Inicio de ejecuci�n
// Suscripci�n 2: 0.456
// (Se ejecuta 2 veces, valores diferentes)Caracter�sticas de Hot: Ejecuci�n compartida
import { Subject } from 'rxjs';
const hot$ = new Subject<number>();
hot$.subscribe(v => console.log('Suscripci�n 1:', v));
hot$.subscribe(v => console.log('Suscripci�n 2:', v));
hot$.next(Math.random());
// Salida:
// Suscripci�n 1: 0.789
// Suscripci�n 2: 0.789
// (Mismo valor compartido)Criterios de selecci�n
| Requisito | Cold | Hot |
|---|---|---|
| Necesitar ejecuci�n independiente | L | |
| Compartir ejecuci�n | L | |
| Valores diferentes por suscriptor | L | |
| Entrega de datos en tiempo real | L | |
| Compartir llamadas API | L (convertir con share) |
Buen ejemplo: Conversi�n apropiada
import { interval, fromEvent } from 'rxjs';
import { share, shareReplay } from 'rxjs';
// Cold: Cada suscriptor tiene temporizador independiente
const coldTimer$ = interval(1000);
// Cold�Hot: Compartir temporizador
const hotTimer$ = interval(1000).pipe(share());
// Cold: Eventos de clic (registro de listener independiente por suscripci�n)
const clicks$ = fromEvent(document, 'click');
// Cold�Hot: Cachear resultados de API
const cachedData$ = ajax.getJSON('/api/data').pipe(
shareReplay({ bufferSize: 1, refCount: true })
);Patr�n de gesti�n centralizada de estado
Patr�n 1: Gesti�n de estado con clase Service
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs';
interface User {
id: number;
name: string;
email: string;
}
class UserStore {
// BehaviorSubject privado
private users$ = new BehaviorSubject<User[]>([]);
// Observable p�blico de solo lectura
get users(): Observable<User[]> {
return this.users$.asObservable();
}
// Obtener usuario espec�fico
getUser(id: number): Observable<User | undefined> {
return this.users.pipe(
map(users => users.find(u => u.id === id))
);
}
// Actualizar estado
addUser(user: User) {
const currentUsers = this.users$.value;
this.users$.next([...currentUsers, user]);
}
updateUser(id: number, updates: Partial<User>) {
const currentUsers = this.users$.value;
const updatedUsers = currentUsers.map(u =>
u.id === id ? { ...u, ...updates } : u
);
this.users$.next(updatedUsers);
}
removeUser(id: number) {
const currentUsers = this.users$.value;
this.users$.next(currentUsers.filter(u => u.id !== id));
}
}
// Uso
const store = new UserStore();
// Suscripci�n
store.users.subscribe(users => {
console.log('Lista de usuarios:', users);
});
// Actualizaci�n de estado
store.addUser({ id: 1, name: 'Alice', email: 'alice@example.com' });
store.updateUser(1, { name: 'Alice Smith' });Patr�n 2: Gesti�n de estado con Scan
import { Subject } from 'rxjs';
import { scan, startWith } from 'rxjs';
interface State {
count: number;
items: string[];
}
type Action =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'ADD_ITEM'; payload: string }
| { type: 'RESET' };
const actions$ = new Subject<Action>();
const initialState: State = {
count: 0,
items: []
};
const state$ = actions$.pipe(
scan((state, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'ADD_ITEM':
return { ...state, items: [...state.items, action.payload] };
case 'RESET':
return initialState;
default:
return state;
}
}, initialState),
startWith(initialState)
);
// Suscripci�n
state$.subscribe(state => {
console.log('Estado actual:', state);
});
// Emitir acciones
actions$.next({ type: 'INCREMENT' });
actions$.next({ type: 'ADD_ITEM', payload: 'manzana' });
actions$.next({ type: 'INCREMENT' });
// Salida:
// Estado actual: { count: 0, items: [] }
// Estado actual: { count: 1, items: [] }
// Estado actual: { count: 1, items: ['manzana'] }
// Estado actual: { count: 2, items: ['manzana'] }Trampas comunes
Trampa 1: Exponer Subject externamente
L Mal ejemplo: Exponer Subject directamente
import { BehaviorSubject } from 'rxjs';
class BadService {
// L Se puede modificar directamente desde fuera
public state$ = new BehaviorSubject<number>(0);
}
const service = new BadService();
// Se puede modificar desde fuera
service.state$.next(999); // L Encapsulaci�n rotaBuen ejemplo: Proteger con asObservable()
import { BehaviorSubject } from 'rxjs';
class GoodService {
private _state$ = new BehaviorSubject<number>(0);
// Exponer como solo lectura
get state() {
return this._state$.asObservable();
}
// Solo modificable a trav�s de m�todos dedicados
increment() {
this._state$.next(this._state$.value + 1);
}
decrement() {
this._state$.next(this._state$.value - 1);
}
}
const service = new GoodService();
// Solo lectura posible
service.state.subscribe(value => console.log(value));
// Modificaci�n a trav�s de m�todos dedicados
service.increment();
// L No se puede modificar directamente (error de compilaci�n)
// service.state.next(999); // Error: Property 'next' does not existTrampa 2: Memory leak con shareReplay
L Mal ejemplo: Memory leak con refCount: false
import { interval } from 'rxjs';
import { shareReplay, take } from 'rxjs';
const data$ = interval(1000).pipe(
take(100),
shareReplay({ bufferSize: 1, refCount: false })
// L refCount: false � Cach� permanece para siempre
);
// Aunque se cancele la suscripci�n, el stream contin�a internamente
const sub = data$.subscribe();
sub.unsubscribe();
// Cach� permanece � Memory leakBuen ejemplo: Limpieza autom�tica con refCount: true
import { interval } from 'rxjs';
import { shareReplay, take } from 'rxjs';
const data$ = interval(1000).pipe(
take(100),
shareReplay({ bufferSize: 1, refCount: true })
// refCount: true � Limpieza autom�tica al cancelar todas las suscripciones
);
const sub1 = data$.subscribe();
const sub2 = data$.subscribe();
sub1.unsubscribe();
sub2.unsubscribe(); // Todas las suscripciones canceladas � Stream detenido, cach� limpiadoTrampa 3: Obtenci�n s�ncrona de valores
L Mal ejemplo: Depender demasiado de value
import { BehaviorSubject } from 'rxjs';
class CounterService {
private count$ = new BehaviorSubject(0);
increment() {
// L Depender demasiado de value
const current = this.count$.value;
this.count$.next(current + 1);
}
// L Exponer obtenci�n s�ncrona
getCurrentCount(): number {
return this.count$.value;
}
}Buen ejemplo: Mantener reactivo
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs';
class CounterService {
private count$ = new BehaviorSubject(0);
get count() {
return this.count$.asObservable();
}
increment() {
// Usar value internamente est� bien
this.count$.next(this.count$.value + 1);
}
// Devolver Observable
isPositive() {
return this.count$.pipe(
map(count => count > 0)
);
}
}Lista de verificaci�n de comprensi�n
Verifique si puede responder las siguientes preguntas.
## Comprensi�n b�sica
- [ ] Explicar las diferencias entre Subject, BehaviorSubject y ReplaySubject
- [ ] Entender por qu� BehaviorSubject requiere un valor inicial
- [ ] Entender el significado del bufferSize de ReplaySubject
## Hot/Cold
- [ ] Explicar las diferencias entre Observable Cold y Hot
- [ ] Explicar las diferencias entre share y shareReplay
- [ ] Entender el rol de la opci�n refCount de shareReplay
## Gesti�n de estado
- [ ] Proteger Subject sin exponerlo externamente usando asObservable()
- [ ] Implementar patr�n de gesti�n de estado con BehaviorSubject
- [ ] Entender el patr�n de gesti�n de estado con scan
## Gesti�n de memoria
- [ ] Saber c�mo prevenir memory leaks de shareReplay
- [ ] Explicar las diferencias entre refCount: true y false
- [ ] Limpiar cach� en el momento apropiadoSiguientes pasos
Despu�s de entender la gesti�n y compartici�n de estado, aprenda sobre combinaci�n de m�ltiples streams.
� Combinaci�n de m�ltiples streams - Diferencias entre combineLatest, zip y withLatestFrom
P�ginas relacionadas
- Chapter 5: �Qu� es Subject? - Fundamentos de Subject
- Chapter 5: Tipos de Subject - Detalles de BehaviorSubject y ReplaySubject
- Operador share() - Explicaci�n detallada de share
- [Mal uso de shareReplay](/es/guide/anti-patterns/common-mistakes#4-sharereplay-n�() - Errores comunes
- Cold vs Hot Observable - Detalles de Cold/Hot
<� Ejercicios pr�cticos
Problema 1: Selecci�n apropiada de Subject
Elija el Subject m�s adecuado para los siguientes escenarios.
- Gestionar estado de login de usuario (Estado inicial: desconectado)
- Entrega de mensajes de notificaci�n (Solo mostrar mensajes despu�s de la suscripci�n)
- Mantener las �ltimas 5 operaciones del historial (Ver las �ltimas 5 aunque se suscriba tarde)
Ejemplo de respuesta
1. Estado de login de usuario
import { BehaviorSubject } from 'rxjs';
class AuthService {
private isLoggedIn$ = new BehaviorSubject<boolean>(false);
get loginStatus() {
return this.isLoggedIn$.asObservable();
}
login() {
this.isLoggedIn$.next(true);
}
logout() {
this.isLoggedIn$.next(false);
}
}Raz�n
Como se necesita el estado actual inmediatamente al suscribirse, BehaviorSubject es �ptimo.
2. Entrega de mensajes de notificaci�n
import { Subject } from 'rxjs';
class NotificationService {
private notifications$ = new Subject<string>();
get messages() {
return this.notifications$.asObservable();
}
notify(message: string) {
this.notifications$.next(message);
}
}Raz�n
Como solo se necesitan mostrar mensajes despu�s de la suscripci�n, Subject es suficiente.
3. �ltimas 5 operaciones del historial
import { ReplaySubject } from 'rxjs';
class HistoryService {
private actions$ = new ReplaySubject<string>(5); // Mantener 5
get history() {
return this.actions$.asObservable();
}
addAction(action: string) {
this.actions$.next(action);
}
}Raz�n
Para mantener las �ltimas 5 y poder obtenerlas aunque se suscriba tarde, ReplaySubject(5) es �ptimo.
Problema 2: Selecci�n de share o shareReplay
Elija el operador apropiado para los siguientes c�digos.
import { ajax } from 'rxjs/ajax';
// Escenario 1: Datos en tiempo real desde WebSocket
const realTimeData$ = webSocket('ws://example.com/stream');
// Escenario 2: Llamada API de informaci�n de usuario (quiere cachear resultado)
const user$ = ajax.getJSON('/api/user/me');
// �Qu� usar para cada uno?Ejemplo de respuesta
Escenario 1: Datos en tiempo real desde WebSocket
import { share } from 'rxjs';
const realTimeData$ = webSocket('ws://example.com/stream').pipe(
share() // Datos en tiempo real no necesitan cach�
);Raz�n
Los datos en tiempo real como WebSocket no necesitan cachear valores pasados, por lo que se usa share(). Si se suscribe tarde, recibir� nuevos datos desde ese punto.
Escenario 2: Llamada API de informaci�n de usuario
import { shareReplay } from 'rxjs';
const user$ = ajax.getJSON('/api/user/me').pipe(
shareReplay({ bufferSize: 1, refCount: true })
);Raz�n
Como se quiere cachear el resultado de la API y compartirlo entre m�ltiples componentes, se usa shareReplay(). refCount: true previene memory leaks.
Problema 3: Correcci�n de memory leak
El siguiente c�digo tiene un problema de memory leak. Corr�jalo.
import { interval } from 'rxjs';
import { shareReplay } from 'rxjs';
const data$ = interval(1000).pipe(
shareReplay(1) // Problema: esto es igual a shareReplay({ bufferSize: 1, refCount: false })
);
const sub = data$.subscribe(v => console.log(v));
sub.unsubscribe();
// Despu�s de esto, interval sigue ejecut�ndose � Memory leakEjemplo de respuesta
C�digo corregido:
import { interval } from 'rxjs';
import { shareReplay } from 'rxjs';
const data$ = interval(1000).pipe(
shareReplay({ bufferSize: 1, refCount: true })
// refCount: true � Stream se detiene al cancelar todas las suscripciones
);
const sub = data$.subscribe(v => console.log(v));
sub.unsubscribe(); // Stream se detieneProblema
shareReplay(1)es la forma abreviada deshareReplay({ bufferSize: 1, refCount: false })- Con
refCount: false, el stream contin�a ejecut�ndose despu�s de cancelar todas las suscripciones - interval sigue ejecut�ndose eternamente, causando memory leak
Raz�n de la correcci�n
Especificando refCount: true, el stream tambi�n se detiene cuando se cancela la �ltima suscripci�n, y se limpia la cach�.
Problema 4: Implementaci�n de gesti�n de estado
Implemente un TodoStore que cumpla los siguientes requisitos.
Requisitos
- Poder agregar, completar y eliminar items Todo
- Obtener lista de Todos como solo lectura externamente
- Obtener n�mero de Todos completados
Ejemplo de respuesta
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs';
interface Todo {
id: number;
text: string;
completed: boolean;
}
class TodoStore {
private todos$ = new BehaviorSubject<Todo[]>([]);
private nextId = 1;
// Exponer como solo lectura
get todos(): Observable<Todo[]> {
return this.todos$.asObservable();
}
// N�mero de Todos completados
get completedCount(): Observable<number> {
return this.todos$.pipe(
map(todos => todos.filter(t => t.completed).length)
);
}
// Agregar Todo
addTodo(text: string) {
const currentTodos = this.todos$.value;
const newTodo: Todo = {
id: this.nextId++,
text,
completed: false
};
this.todos$.next([...currentTodos, newTodo]);
}
// Completar Todo
toggleTodo(id: number) {
const currentTodos = this.todos$.value;
const updatedTodos = currentTodos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
);
this.todos$.next(updatedTodos);
}
// Eliminar Todo
removeTodo(id: number) {
const currentTodos = this.todos$.value;
this.todos$.next(currentTodos.filter(todo => todo.id !== id));
}
}
// Uso
const store = new TodoStore();
store.todos.subscribe(todos => {
console.log('Lista de Todos:', todos);
});
store.completedCount.subscribe(count => {
console.log('Completados:', count);
});
store.addTodo('Aprender RxJS');
store.addTodo('Leer documentaci�n');
store.toggleTodo(1);Puntos clave
- Mantener estado con
BehaviorSubject - Exponer externamente como solo lectura con
asObservable() - Usar
valuepara obtener y actualizar el estado actual - Usar
mappara calcular estado derivado (completedCount)