Skip to content

concatAll - Aplanar Observables Internos Secuencialmente ​

El operador concatAll toma un Higher-order Observable (Observable de Observables), se suscribe a Observables internos en orden, y aplana sus valores. No comenzará el siguiente hasta que el Observable anterior complete.

🔰 Sintaxis Básica y Uso ​

ts
import { fromEvent, interval } from 'rxjs';
import { map, concatAll, take } from 'rxjs';

const clicks$ = fromEvent(document, 'click');

// Comenzar un nuevo contador para cada clic (Higher-order Observable)
const higherOrder$ = clicks$.pipe(
  map(() => interval(1000).pipe(take(3)))
);

// Suscribirse a contadores en orden (comenzar siguiente después de que el anterior completa)
higherOrder$
  .pipe(concatAll())
  .subscribe(x => console.log(x));

// Salida (con 3 clics):
// 0 (1er contador)
// 1 (1er contador)
// 2 (1er contador) ← Completo
// 0 (2do contador) ← Comenzar después de que el 1ro completa
// 1 (2do contador)
// 2 (2do contador) ← Completo
// 0 (3er contador) ← Comenzar después de que el 2do completa
// ...
  • Suscribirse en orden a cada Observable interno emitido desde Higher-order Observable
  • No comenzar el siguiente hasta que el Observable interno anterior complete
  • El orden de los valores está garantizado

🌐 Documentación Oficial de RxJS - concatAll

💡 Patrones de Uso Típicos ​

  • Ejecutar llamadas API en secuencia (ejecutar siguiente después de que la solicitud anterior completa)
  • Reproducir animaciones en orden
  • Procesar cargas de archivos secuencialmente

🧠 Ejemplo de Código Práctico ​

Ejemplo de ejecución de llamadas API (simuladas) en orden para cada clic de botón

ts
import { fromEvent, of } from 'rxjs';
import { map, concatAll, delay } from 'rxjs';

const button = document.createElement('button');
button.textContent = 'Llamada API';
document.body.appendChild(button);

const output = document.createElement('div');
document.body.appendChild(output);

let callCount = 0;

// Evento de clic de botón
const clicks$ = fromEvent(button, 'click');

// Higher-order Observable: Llamada API simulada para cada clic
const results$ = clicks$.pipe(
  map(() => {
    const id = ++callCount;
    const start = Date.now();

    // Llamada API simulada (retraso de 2 segundos)
    return of(`Llamada API #${id} completada`).pipe(
      delay(2000),
      map(msg => {
        const elapsed = ((Date.now() - start) / 1000).toFixed(1);
        return `${msg} (${elapsed} segundos)`;
      })
    );
  }),
  concatAll() // Ejecutar todas las llamadas API en orden
);

results$.subscribe(result => {
  const item = document.createElement('div');
  item.textContent = result;
  output.prepend(item);
});
  • Incluso con clics consecutivos de botón, las llamadas API se ejecutan en orden
  • La siguiente llamada API comienza después de que la anterior completa

🔄 Operadores Relacionados ​

OperadorDescripción
concatMapAtajo para map + concatAll (comúnmente usado)
mergeAllSuscribirse a todos los Observables internos en paralelo
switchAllCambiar a nuevo Observable interno (cancelar antiguo)
exhaustAllIgnorar nuevos Observables internos mientras se ejecuta

⚠️ Notas Importantes ​

Backpressure (Acumulación de Cola) ​

Si la tasa de emisión del Observable interno es más rápida que la tasa de completación, los Observables no procesados se acumularán en la cola.

ts
// Clic cada segundo → La llamada API toma 2 segundos
// → La cola puede acumularse continuamente

En este caso, considere estas contramedidas:

  • Usar switchAll (procesar solo el último)
  • Usar exhaustAll (ignorar durante la ejecución)
  • Agregar debounce o throttling

Cuidado con Observables Infinitos ​

Si el Observable anterior nunca completa, el siguiente nunca comenzará.

❌ interval nunca completa, por lo que el 2do contador nunca comienza ​

ts
clicks$.pipe(
  map(() => interval(1000)), // Nunca completa
  concatAll()
).subscribe();

✅ Completar con take ​

ts
clicks$.pipe(
  map(() => interval(1000).pipe(take(3))), // Completa después de 3
  concatAll()
).subscribe();

Publicado bajo licencia CC-BY-4.0.