Skip to content

Combinaci�n de m�ltiples streams

En RxJS, requisitos como "combinar resultados de 2 APIs" o "monitorear todos los campos de un formulario" son muy comunes, pero elegir el operador apropiado es dif�cil. Esta p�gina explica patrones pr�cticos para combinar m�ltiples streams.

combineLatest vs zip vs withLatestFrom vs forkJoin

Comparaci�n de los 4 operadores de combinaci�n principales

OperadorMomento de emisi�nC�mo combina valoresCondici�n de completadoCasos de uso comunes
combineLatestCuando cualquiera cambia�ltimos valores de cada streamTodos los streams completanValidaci�n de formularios, combinaci�n de configuraci�n
zipCuando todos los streams emiten valorEmparejar valores en posiciones correspondientesCualquiera completaPaginaci�n, sincronizaci�n de procesamiento paralelo
withLatestFromCuando el stream principal cambiaPrincipal + �ltimo valor auxiliarStream principal completaEvento + estado actual
forkJoinTodos los streams completanValores finales de cada streamTodos los streams completanLlamadas paralelas a m�ltiples APIs

Comparaci�n con Marble Diagrams

A:  --1--2--------3----|
B:  ----a----b------c----|

combineLatest(A, B):
    ----[1,a]-[2,a]-[2,b]-[3,b]-[3,c]|
    (Emite cada vez que cualquiera cambia)

zip(A, B):
    ----[1,a]----[2,b]----[3,c]|
    (Empareja en posiciones correspondientes)

A.pipe(withLatestFrom(B)):
    ----[1,a]----[2,b]----[3,c]|
    (Solo emite cuando A cambia)

forkJoin({ a: A, b: B }):
    ---------------------------{ a: 3, b: c }|
    (Emite despu�s de que ambos completen)

Visualizaci�n del momento de emisi�n

El siguiente diagrama muestra cu�ndo cada operador de combinaci�n emite valores.

Criterios de selecci�n

  • combineLatest: Combinaci�n reactiva de estados (formularios, configuraci�n)
  • zip: Emparejar valores correspondientes (paginaci�n, procesamiento paralelo)
  • withLatestFrom: Evento + estado actual (obtener configuraci�n al hacer clic)
  • forkJoin: Ejecutar m�ltiples procesos as�ncronos en paralelo y obtener todos los resultados (m�ltiples APIs)

combineLatest: Combinaci�n de �ltimos valores

Caracter�sticas

  • Despu�s de que todos los streams emitan al menos una vez, emite cada vez que cualquiera cambia
  • Combina los �ltimos valores de cada stream
  • Contin�a hasta que todos los streams completen

Ejemplo pr�ctico 1: Validaci�n de formularios

L Mal ejemplo: Suscribirse individualmente y combinar manualmente

typescript
import { BehaviorSubject } from 'rxjs';

const email$ = new BehaviorSubject('');
const password$ = new BehaviorSubject('');
let isValid = false;

email$.subscribe(email => {
  // Se necesita el valor de password$ pero no se puede obtener
  // Necesita gestionarse con variables globales, etc.
});

password$.subscribe(password => {
  // Mismo problema
});

 Buen ejemplo: Combinaci�n autom�tica con combineLatest

typescript
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map } from 'rxjs';

const email$ = new BehaviorSubject('');
const password$ = new BehaviorSubject('');

const isFormValid$ = combineLatest([email$, password$]).pipe(
  map(([email, password]) => {
    const emailValid = email.includes('@') && email.length > 3;
    const passwordValid = password.length >= 8;
    return emailValid && passwordValid;
  })
);

isFormValid$.subscribe(isValid => {
  console.log('Formulario v�lido:', isValid);
});

// Cambio de valores
email$.next('user@example.com');  // Formulario v�lido: false (contrase�a corta)
password$.next('pass1234');       // Formulario v�lido: true

Ejemplo pr�ctico 2: Combinaci�n de m�ltiples valores de configuraci�n

typescript
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map } from 'rxjs';

interface Config {
  theme: 'light' | 'dark';
  language: 'ja' | 'en';
  fontSize: number;
}

const theme$ = new BehaviorSubject<'light' | 'dark'>('light');
const language$ = new BehaviorSubject<'ja' | 'en'>('ja');
const fontSize$ = new BehaviorSubject<number>(14);

const config$ = combineLatest([theme$, language$, fontSize$]).pipe(
  map(([theme, language, fontSize]): Config => ({
    theme,
    language,
    fontSize
  }))
);

config$.subscribe(config => {
  console.log('Configuraci�n actualizada:', config);
  // Proceso para actualizar UI
});

theme$.next('dark');      // Configuraci�n actualizada: { theme: 'dark', language: 'ja', fontSize: 14 }
fontSize$.next(16);       // Configuraci�n actualizada: { theme: 'dark', language: 'ja', fontSize: 16 }

Cu�ndo usar combineLatest

  • Validaci�n de formularios: Combinar �ltimos valores de todos los campos
  • Monitoreo de configuraci�n: Reaccionar cuando cambian m�ltiples configuraciones
  • Visualizaci�n dependiente: Actualizar UI seg�n m�ltiples estados
  • Filtrado: Combinar m�ltiples condiciones

zip: Emparejar en posiciones correspondientes

Caracter�sticas

  • Empareja valores en posiciones correspondientes de cada stream
  • Espera hasta que valores de todos los streams est�n listos
  • Completa cuando cualquier stream completa

Ejemplo pr�ctico 1: Emparejar datos y metadatos en paginaci�n

L Mal ejemplo: El timing se desincroniza

typescript
import { interval } from 'rxjs';
import { map, take } from 'rxjs';

// Obtenci�n de datos de p�gina (lento)
const pages$ = interval(1000).pipe(
  map(i => `Datos de p�gina ${i + 1}`),
  take(3)
);

// Obtenci�n de metadatos (r�pido)
const metadata$ = interval(100).pipe(
  map(i => `Metadatos ${i + 1}`),
  take(3)
);

// Suscribirse individualmente rompe la correspondencia
pages$.subscribe(page => console.log('P�gina:', page));
metadata$.subscribe(meta => console.log('Meta:', meta));

// Salida:
// Meta: Metadatos 1
// Meta: Metadatos 2
// Meta: Metadatos 3
// P�gina: Datos de p�gina 1
// P�gina: Datos de p�gina 2
// P�gina: Datos de p�gina 3
// (Correspondencia desordenada)

 Buen ejemplo: Emparejar posiciones correspondientes con zip

typescript
import { interval, zip } from 'rxjs';
import { map, take } from 'rxjs';

const pages$ = interval(1000).pipe(
  map(i => `Datos de p�gina ${i + 1}`),
  take(3)
);

const metadata$ = interval(100).pipe(
  map(i => `Metadatos ${i + 1}`),
  take(3)
);

zip(pages$, metadata$).subscribe(([page, meta]) => {
  console.log(`${page} - ${meta}`);
});

// Salida (cada segundo):
// Datos de p�gina 1 - Metadatos 1
// Datos de p�gina 2 - Metadatos 2
// Datos de p�gina 3 - Metadatos 3

Ejemplo pr�ctico 2: Obtener resultados de procesamiento paralelo en orden

typescript
import { of, zip } from 'rxjs';
import { delay, map } from 'rxjs';

// Llamar 3 APIs en paralelo, pero tiempos de completado var�an
const api1$ = of('Resultado 1').pipe(delay(300));
const api2$ = of('Resultado 2').pipe(delay(100)); // M�s r�pido
const api3$ = of('Resultado 3').pipe(delay(200));

zip(api1$, api2$, api3$).pipe(
  map(([r1, r2, r3]) => ({ r1, r2, r3 }))
).subscribe(results => {
  console.log('Todos los resultados:', results);
});

// Salida (despu�s de 300ms, cuando todos est�n listos):
// Todos los resultados: { r1: 'Resultado 1', r2: 'Resultado 2', r3: 'Resultado 3' }

Cu�ndo usar zip

  • El orden es importante: Emparejar 1� con 1�, 2� con 2�
  • Emparejar datos y metadatos: Datos de p�gina con n�mero de p�gina
  • Sincronizaci�n de procesamiento paralelo: Ejecutar m�ltiples procesos en paralelo garantizando el orden

Advertencia sobre zip

  • Espera al stream m�s lento, por lo que puede acumular b�fer
  • Con streams infinitos, puede causar memory leaks al arrastrarse por el m�s lento

withLatestFrom: Obtener valor principal + auxiliar

Caracter�sticas

  • Solo emite cuando el stream principal emite un valor
  • Obtiene el �ltimo valor del stream auxiliar y lo combina
  • Completa cuando el stream principal completa

Ejemplo pr�ctico 1: Evento de clic + estado actual

L Mal ejemplo: combineLatest emite innecesariamente

typescript
import { fromEvent, BehaviorSubject, combineLatest } from 'rxjs';

const button = document.querySelector('button')!;
const clicks$ = fromEvent(button, 'click');
const counter$ = new BehaviorSubject(0);

// L combineLatest tambi�n emite cada vez que counter$ cambia
combineLatest([clicks$, counter$]).subscribe(([event, count]) => {
  console.log('Contador al hacer clic:', count);
});

// Emite cada vez que counter$ cambia
setInterval(() => {
  counter$.next(counter$.value + 1); // Emisi�n innecesaria
}, 1000);

 Buen ejemplo: Solo emitir al hacer clic con withLatestFrom

typescript
import { fromEvent, BehaviorSubject } from 'rxjs';
import { withLatestFrom } from 'rxjs';

const button = document.querySelector('button')!;
const clicks$ = fromEvent(button, 'click');
const counter$ = new BehaviorSubject(0);

clicks$.pipe(
  withLatestFrom(counter$)
).subscribe(([event, count]) => {
  console.log('Contador al hacer clic:', count);
});

// No emite cuando counter$ cambia
setInterval(() => {
  counter$.next(counter$.value + 1); //  No emite
}, 1000);

Ejemplo pr�ctico 2: Env�o de formulario + informaci�n de usuario actual

typescript
import { fromEvent, BehaviorSubject } from 'rxjs';
import { withLatestFrom, map } from 'rxjs';

const submitButton = document.querySelector('#submit')!;
const submit$ = fromEvent(submitButton, 'click');

const currentUser$ = new BehaviorSubject({ id: 1, name: 'Alice' });
const formData$ = new BehaviorSubject({ title: '', content: '' });

submit$.pipe(
  withLatestFrom(currentUser$, formData$),
  map(([event, user, data]) => ({
    ...data,
    authorId: user.id,
    authorName: user.name,
    timestamp: Date.now()
  }))
).subscribe(payload => {
  console.log('Datos de env�o:', payload);
  // Enviar a API...
});

Cu�ndo usar withLatestFrom

  • Evento + estado: Obtener estado actual al hacer clic
  • Proceso principal + datos auxiliares: Informaci�n de usuario al enviar formulario
  • Trigger + configuraci�n: Valores de configuraci�n actuales al hacer clic en bot�n

forkJoin: Esperar completado de todos

Caracter�sticas

  • Espera hasta que todos los streams completen
  • Obtiene el valor final de cada stream
  • Equivalente a Promise.all() de Promise

Ejemplo pr�ctico 1: Llamadas paralelas a m�ltiples APIs

L Mal ejemplo: Ejecuci�n secuencial lenta

typescript
import { ajax } from 'rxjs/ajax';

ajax.getJSON('/api/user').subscribe(user => {
  console.log('Usuario obtenido:', user);

  ajax.getJSON('/api/posts').subscribe(posts => {
    console.log('Posts obtenidos:', posts);

    ajax.getJSON('/api/comments').subscribe(comments => {
      console.log('Comentarios obtenidos:', comments);
      // Anidamiento profundo
    });
  });
});

 Buen ejemplo: Ejecuci�n paralela con forkJoin

typescript
import { forkJoin } from 'rxjs';
import { ajax } from 'rxjs/ajax';

forkJoin({
  user: ajax.getJSON('/api/user'),
  posts: ajax.getJSON('/api/posts'),
  comments: ajax.getJSON('/api/comments')
}).subscribe(({ user, posts, comments }) => {
  console.log('Todos los datos obtenidos:', { user, posts, comments });
  // 3 APIs ejecutadas en paralelo
});

Ejemplo pr�ctico 2: Subida de m�ltiples archivos

typescript
import { forkJoin, Observable, of } from 'rxjs';
import { delay } from 'rxjs';

function uploadFile(file: File): Observable<string> {
  return of(`${file.name} subida completa`).pipe(
    delay(Math.random() * 2000)
  );
}

const files = [
  new File([''], 'archivo1.txt'),
  new File([''], 'archivo2.txt'),
  new File([''], 'archivo3.txt')
];

forkJoin(files.map(file => uploadFile(file))).subscribe(results => {
  console.log('Subida de todos los archivos completa:', results);
  // Se muestra despu�s de que todas las subidas completen
});

Cu�ndo usar forkJoin

  • Llamadas paralelas a m�ltiples APIs: Obtenci�n masiva de datos iniciales
  • Procesamiento por lotes: Completar todas las tareas
  • Ejecuci�n paralela de procesos independientes: Cuando cada proceso es independiente

Advertencia sobre forkJoin

  • No se puede usar con streams que no completan (como interval)
  • Si uno falla, todo falla
  • No se pueden obtener valores intermedios (solo valores finales)

Diagrama de flujo de selecci�n

Patrones pr�cticos

Patr�n 1: Validaci�n de formularios

typescript
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map } from 'rxjs';

interface FormState {
  email: string;
  password: string;
  agreeToTerms: boolean;
}

class RegistrationForm {
  private email$ = new BehaviorSubject('');
  private password$ = new BehaviorSubject('');
  private agreeToTerms$ = new BehaviorSubject(false);

  readonly isValid$ = combineLatest([
    this.email$,
    this.password$,
    this.agreeToTerms$
  ]).pipe(
    map(([email, password, agreed]) => {
      const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
      const passwordValid = password.length >= 8;
      return emailValid && passwordValid && agreed;
    })
  );

  readonly formState$ = combineLatest([
    this.email$,
    this.password$,
    this.agreeToTerms$
  ]).pipe(
    map(([email, password, agreeToTerms]): FormState => ({
      email,
      password,
      agreeToTerms
    }))
  );

  updateEmail(email: string) {
    this.email$.next(email);
  }

  updatePassword(password: string) {
    this.password$.next(password);
  }

  toggleTerms() {
    this.agreeToTerms$.next(!this.agreeToTerms$.value);
  }
}

// Uso
const form = new RegistrationForm();

form.isValid$.subscribe(isValid => {
  console.log('Formulario v�lido:', isValid);
});

form.updateEmail('user@example.com');
form.updatePassword('password123');
form.toggleTerms();

Patr�n 2: Llamadas API con dependencias

typescript
import { forkJoin, of } from 'rxjs';
import { switchMap, map, catchError } from 'rxjs';
import { ajax } from 'rxjs/ajax';

interface User {
  id: number;
  name: string;
}

interface Post {
  id: number;
  userId: number;
  title: string;
}

interface Comment {
  id: number;
  postId: number;
  text: string;
}

// Obtener posts y comentarios del usuario en paralelo
function getUserData(userId: number) {
  return ajax.getJSON<User>(`/api/users/${userId}`).pipe(
    switchMap(user =>
      forkJoin({
        user: of(user),
        posts: ajax.getJSON<Post[]>(`/api/users/${userId}/posts`),
        comments: ajax.getJSON<Comment[]>(`/api/users/${userId}/comments`)
      })
    ),
    catchError(error => {
      console.error('Error:', error);
      return of({
        user: null,
        posts: [],
        comments: []
      });
    })
  );
}

// Uso
getUserData(1).subscribe(({ user, posts, comments }) => {
  console.log('Datos de usuario:', { user, posts, comments });
});

Patr�n 3: Filtrado en tiempo real

typescript
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map } from 'rxjs';

interface Product {
  id: number;
  name: string;
  category: string;
  price: number;
}

class ProductFilter {
  private products$ = new BehaviorSubject<Product[]>([
    { id: 1, name: 'Port�til', category: 'electronics', price: 100000 },
    { id: 2, name: 'Mouse', category: 'electronics', price: 2000 },
    { id: 3, name: 'Libro', category: 'books', price: 1500 }
  ]);

  private searchQuery$ = new BehaviorSubject('');
  private categoryFilter$ = new BehaviorSubject<string | null>(null);
  private maxPrice$ = new BehaviorSubject<number>(Infinity);

  readonly filteredProducts$ = combineLatest([
    this.products$,
    this.searchQuery$,
    this.categoryFilter$,
    this.maxPrice$
  ]).pipe(
    map(([products, query, category, maxPrice]) => {
      return products.filter(p => {
        const matchesQuery = p.name.toLowerCase().includes(query.toLowerCase());
        const matchesCategory = !category || p.category === category;
        const matchesPrice = p.price <= maxPrice;
        return matchesQuery && matchesCategory && matchesPrice;
      });
    })
  );

  updateSearch(query: string) {
    this.searchQuery$.next(query);
  }

  updateCategory(category: string | null) {
    this.categoryFilter$.next(category);
  }

  updateMaxPrice(price: number) {
    this.maxPrice$.next(price);
  }
}

// Uso
const filter = new ProductFilter();

filter.filteredProducts$.subscribe(products => {
  console.log('Productos filtrados:', products);
});

filter.updateSearch('Mouse');
filter.updateCategory('electronics');
filter.updateMaxPrice(50000);

Trampas comunes

Trampa 1: Primera emisi�n de combineLatest

L Mal ejemplo: Stream sin valor inicial

typescript
import { Subject, combineLatest } from 'rxjs';

const a$ = new Subject<number>();
const b$ = new Subject<number>();

combineLatest([a$, b$]).subscribe(([a, b]) => {
  console.log('Valores:', a, b);
});

a$.next(1); // No se imprime nada (b$ a�n no emiti� valor)
b$.next(2); // Aqu� se imprime por primera vez: Valores: 1 2

 Buen ejemplo: Configurar valor inicial con BehaviorSubject

typescript
import { BehaviorSubject, combineLatest } from 'rxjs';

const a$ = new BehaviorSubject<number>(0); // Valor inicial
const b$ = new BehaviorSubject<number>(0);

combineLatest([a$, b$]).subscribe(([a, b]) => {
  console.log('Valores:', a, b);
});

// Salida: Valores: 0 0 (emite inmediatamente)

a$.next(1); // Salida: Valores: 1 0
b$.next(2); // Salida: Valores: 1 2

Trampa 2: Acumulaci�n de b�fer con zip

L Mal ejemplo: B�fer se acumula con stream lento

typescript
import { interval, zip } from 'rxjs';
import { take } from 'rxjs';

const fast$ = interval(100).pipe(take(100));  // R�pido
const slow$ = interval(1000).pipe(take(10));  // Lento

zip(fast$, slow$).subscribe(([f, s]) => {
  console.log('Par:', f, s);
});

// Problema: valores de fast$ se acumulan en el b�fer
// Hasta que slow$ emita 10, fast$ consume 100 espacios de memoria

 Buen ejemplo: Ajustar velocidad

typescript
import { interval, combineLatest } from 'rxjs';
import { take } from 'rxjs';

const fast$ = interval(100).pipe(take(100));
const slow$ = interval(1000).pipe(take(10));

// Usar combineLatest en lugar de zip
combineLatest([fast$, slow$]).subscribe(([f, s]) => {
  console.log('�ltima combinaci�n:', f, s);
});

// O ajustar fast$ con throttleTime

Trampa 3: Stream infinito con forkJoin

L Mal ejemplo: Stream que no completa

typescript
import { interval, forkJoin } from 'rxjs';
import { ajax } from 'rxjs/ajax';

forkJoin({
  timer: interval(1000),  // L No completa
  user: ajax.getJSON('/api/user')
}).subscribe(result => {
  console.log(result); // Nunca se ejecuta
});

 Buen ejemplo: Delimitar con take

typescript
import { interval, forkJoin } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { take } from 'rxjs';

forkJoin({
  timer: interval(1000).pipe(take(5)), //  Completa despu�s de 5
  user: ajax.getJSON('/api/user')
}).subscribe(result => {
  console.log('Resultado:', result); // Se ejecuta despu�s de 5 segundos
});

Lista de verificaci�n de comprensi�n

Verifique si puede responder las siguientes preguntas.

markdown
## Comprensi�n b�sica
- [ ] Explicar las diferencias entre combineLatest, zip, withLatestFrom y forkJoin
- [ ] Entender el momento de emisi�n de cada uno
- [ ] Explicar cu�ndo completa cada operador

## Selecci�n
- [ ] Elegir el operador apropiado para validaci�n de formularios
- [ ] Elegir el operador apropiado para llamadas paralelas a m�ltiples APIs
- [ ] Elegir el operador apropiado para combinaci�n de evento + estado

## Advertencias
- [ ] Entender condiciones de primera emisi�n de combineLatest
- [ ] Explicar el problema de acumulaci�n de b�fer con zip
- [ ] Entender por qu� no se puede usar forkJoin con streams infinitos

## Pr�ctica
- [ ] Implementar patr�n de validaci�n de formularios
- [ ] Implementar llamadas paralelas a m�ltiples APIs
- [ ] Implementar filtrado en tiempo real

Siguientes pasos

Despu�s de entender la combinaci�n de m�ltiples streams, aprenda sobre t�cnicas de depuraci�n.

T�cnicas de depuraci�n - C�mo depurar streams complejos

P�ginas relacionadas

<� Ejercicios pr�cticos

Problema 1: Selecci�n apropiada de operador

Elija el operador m�s adecuado para los siguientes escenarios.

  1. Habilitar bot�n submit cuando se ingresen nombre de usuario y correo electr�nico
  2. Enviar contenido actual del carrito al hacer clic en bot�n
  3. Llamar 3 APIs en paralelo y mostrar datos cuando todas completen
  4. Emparejar n�mero de p�gina con items por p�gina
Ejemplo de respuesta

1. Habilitar bot�n submit cuando se ingresen nombre de usuario y correo electr�nico

typescript
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map } from 'rxjs';

const username$ = new BehaviorSubject('');
const email$ = new BehaviorSubject('');

const isSubmitEnabled$ = combineLatest([username$, email$]).pipe(
  map(([username, email]) => username.length > 0 && email.length > 0)
);

isSubmitEnabled$.subscribe(enabled => {
  console.log('Submit habilitado:', enabled);
});

Raz�n

Como se necesita re-evaluar cuando cualquiera cambia, combineLatest es �ptimo.


2. Enviar contenido actual del carrito al hacer clic en bot�n

typescript
import { fromEvent, BehaviorSubject } from 'rxjs';
import { withLatestFrom } from 'rxjs';

const submitButton = document.querySelector('#checkout')!;
const submit$ = fromEvent(submitButton, 'click');
const cart$ = new BehaviorSubject<string[]>([]);

submit$.pipe(
  withLatestFrom(cart$)
).subscribe(([event, cart]) => {
  console.log('Compra:', cart);
  // Enviar a API...
});

Raz�n

Como solo se emite al hacer clic (stream principal) y se quiere obtener el �ltimo valor del carrito, withLatestFrom es �ptimo.


3. Llamar 3 APIs en paralelo y mostrar datos cuando todas completen

typescript
import { forkJoin } from 'rxjs';
import { ajax } from 'rxjs/ajax';

forkJoin({
  users: ajax.getJSON('/api/users'),
  products: ajax.getJSON('/api/products'),
  orders: ajax.getJSON('/api/orders')
}).subscribe(({ users, products, orders }) => {
  console.log('Todos los datos obtenidos:', { users, products, orders });
});

Raz�n

Para ejecutar m�ltiples llamadas API en paralelo y esperar hasta que todas completen, forkJoin es �ptimo.


4. Emparejar n�mero de p�gina con items por p�gina

typescript
import { BehaviorSubject, zip } from 'rxjs';

const pageNumber$ = new BehaviorSubject(1);
const itemsPerPage$ = new BehaviorSubject(10);

zip(pageNumber$, itemsPerPage$).subscribe(([page, items]) => {
  console.log(`P�gina ${page}: ${items} items/p�gina`);
});

pageNumber$.next(2);
itemsPerPage$.next(20);

Raz�n

Para emparejar n�mero de p�gina con items en posiciones correspondientes, zip es �ptimo.

Problema 2: Primera emisi�n de combineLatest

�Cu�ndo se imprime el primer valor en el siguiente c�digo?

typescript
import { Subject, BehaviorSubject, combineLatest } from 'rxjs';

const a$ = new Subject<number>();
const b$ = new BehaviorSubject<number>(0);
const c$ = new Subject<number>();

combineLatest([a$, b$, c$]).subscribe(([a, b, c]) => {
  console.log('Valores:', a, b, c);
});

a$.next(1);
c$.next(3);
Respuesta

Respuesta: Cuando se ejecuta c$.next(3);

Salida: Valores: 1 0 3

Raz�n

combineLatest emite despu�s de que todos los streams emitan al menos una vez.

  • a$ es Subject sin valor inicial � valor sale con a$.next(1)
  • b$ es BehaviorSubject con valor inicial 0 � ya tiene valor
  • c$ es Subject sin valor inicial � valor sale con c$.next(3)

Cuando se ejecuta c$.next(3), todos los streams tienen valores, por lo que emite ah�.

Problema 3: Diferencias entre zip y combineLatest

Prediga la salida de zip y combineLatest en el siguiente Marble Diagram.

A:  --1--2----3----|
B:  ----a----b-----|

�Salida de zip(A, B)?
�Salida de combineLatest(A, B)?
Respuesta

Salida de zip(A, B):

----[1,a]----[2,b]-|

Salida de combineLatest(A, B):

----[1,a]-[2,a]-[2,b]-[3,b]|

Raz�n

  • zip: Empareja en posiciones correspondientes
    • 1 con a, 2 con b, 3 no tiene par, completa
  • combineLatest: Emite �ltima combinaci�n cada vez que cualquiera cambia
    • Sale a � [1,a]
    • Sale 2 � [2,a]
    • Sale b � [2,b]
    • Sale 3 � [3,b]

Problema 4: forkJoin con manejo de errores

Escriba c�digo para cuando, en m�ltiples llamadas API, algunas fallen pero se quieran obtener otros datos.

Ejemplo de respuesta
typescript
import { forkJoin, of } from 'rxjs';
import { catchError } from 'rxjs';
import { ajax } from 'rxjs/ajax';

forkJoin({
  users: ajax.getJSON('/api/users').pipe(
    catchError(error => {
      console.error('Fallo al obtener usuarios:', error);
      return of([]); // Devolver array vac�o
    })
  ),
  products: ajax.getJSON('/api/products').pipe(
    catchError(error => {
      console.error('Fallo al obtener productos:', error);
      return of([]);
    })
  ),
  orders: ajax.getJSON('/api/orders').pipe(
    catchError(error => {
      console.error('Fallo al obtener �rdenes:', error);
      return of([]);
    })
  )
}).subscribe(({ users, products, orders }) => {
  console.log('Datos obtenidos:', { users, products, orders });
  // API fallida ser� array vac�o, pero otros datos se obtienen
});

Puntos clave

  • Agregar catchError a cada Observable
  • Devolver valor por defecto (array vac�o, etc.) en caso de error
  • Con esto, aunque algunos fallen, el total completa
  • Posible imprimir error en log y notificar al usuario

Publicado bajo licencia CC-BY-4.0.