concatWith - Concatenar Streams en Secuencia Dentro de un Pipeline
El operador concatWith concatena secuencialmente los otros Observables especificados después de que el Observable original completa. Esta es la versión Pipeable Operator de la Creation Function concat.
🔰 Sintaxis Básica y Uso
ts
import { of, delay } from 'rxjs';
import { concatWith } from 'rxjs';
const obs1$ = of('A', 'B').pipe(delay(100));
const obs2$ = of('C', 'D').pipe(delay(100));
const obs3$ = of('E', 'F').pipe(delay(100));
obs1$
.pipe(concatWith(obs2$, obs3$))
.subscribe(console.log);
// Salida: A → B → C → D → E → F- Después de que
obs1$completa,obs2$inicia, y después de queobs2$completa,obs3$inicia. - Se puede usar dentro de cadenas
.pipe(), facilitando la combinación con otros operadores.
🌐 Documentación Oficial de RxJS - concatWith
💡 Patrones de Uso Típicos
- Procesamiento secuencial dentro de un pipeline: Combinar datos adicionales en secuencia al stream transformado
- Procesamiento de seguimiento después de la completación: Agregar limpieza y notificaciones después de que el procesamiento principal completa
- Carga de datos por etapas: Adquirir datos adicionales secuencialmente después de la adquisición de datos iniciales
🧠 Ejemplo de Código Práctico (con UI)
Ejemplo de mostrar elementos recomendados relacionados en orden después de mostrar los resultados de búsqueda principales.
ts
import { of, delay } from 'rxjs';
import { concatWith, map } from 'rxjs';
// Crear área de salida
const output = document.createElement('div');
output.innerHTML = '<h3>Ejemplo Práctico de concatWith:</h3>';
document.body.appendChild(output);
// Resultados de búsqueda principales
const searchResults$ = of('🔍 Resultado de Búsqueda 1', '🔍 Resultado de Búsqueda 2', '🔍 Resultado de Búsqueda 3').pipe(
delay(500)
);
// Elementos recomendados 1
const recommendations1$ = of('💡 Producto Recomendado A', '💡 Producto Recomendado B').pipe(
delay(300)
);
// Elementos recomendados 2
const recommendations2$ = of('⭐ Producto Popular X', '⭐ Producto Popular Y').pipe(
delay(300)
);
// Combinar en secuencia y mostrar
searchResults$
.pipe(
concatWith(recommendations1$, recommendations2$),
map((value, index) => `${index + 1}. ${value}`)
)
.subscribe((value) => {
const item = document.createElement('div');
item.textContent = value;
output.appendChild(item);
});- Los resultados de búsqueda se muestran primero,
- Luego los productos recomendados se muestran en orden.
- Se puede usar en combinación con otros operadores como
mapdentro del pipeline.
🔄 Diferencia con la Creation Function concat
Diferencias Básicas
concat (Creation Function) | concatWith (Pipeable Operator) | |
|---|---|---|
| Ubicación de Uso | Usado como función independiente | Usado dentro de cadena .pipe() |
| Sintaxis | concat(obs1$, obs2$, obs3$) | obs1$.pipe(concatWith(obs2$, obs3$)) |
| Primer Stream | Trata todos por igual | Trata como stream principal |
| Ventaja | Simple y legible | Fácil de combinar con otros operadores |
Ejemplos de Uso Específicos
Creation Function Recomendada Solo para Combinación Simple
ts
import { concat, of } from 'rxjs';
const part1$ = of('A', 'B');
const part2$ = of('C', 'D');
const part3$ = of('E', 'F');
// Simple y legible
concat(part1$, part2$, part3$).subscribe(console.log);
// Salida: A → B → C → D → E → FPipeable Operator Recomendado si Se Necesita Transformación
ts
import { of } from 'rxjs';
import { concatWith, map, filter } from 'rxjs';
const userData$ = of({ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 });
const additionalData$ = of({ name: 'Charlie', age: 35 });
// ❌ Versión Creation Function - se vuelve verbosa
import { concat } from 'rxjs';
concat(
userData$.pipe(
filter(user => user.age >= 30),
map(user => user.name)
),
additionalData$.pipe(map(user => user.name))
).subscribe(console.log);
// ✅ Versión Pipeable Operator - completada en un pipeline
userData$
.pipe(
filter(user => user.age >= 30), // Solo 30 años o más
map(user => user.name), // Extraer solo nombre
concatWith(
additionalData$.pipe(map(user => user.name))
)
)
.subscribe(console.log);
// Salida: Alice → CharlieCuando Se Agrega Procesamiento Posterior al Stream Principal
ts
import { fromEvent, of } from 'rxjs';
import { concatWith, take, mapTo } from 'rxjs';
// Crear botón y área de salida
const button = document.createElement('button');
button.textContent = 'Haz clic 3 veces';
document.body.appendChild(button);
const output = document.createElement('div');
output.style.marginTop = '10px';
document.body.appendChild(output);
const clicks$ = fromEvent(button, 'click');
// ✅ Versión Pipeable Operator - natural como extensión del stream principal
clicks$
.pipe(
take(3), // Obtener primeros 3 clics
mapTo('Clicado'),
concatWith(of('Completado')) // Agregar mensaje después de completación
)
.subscribe(message => {
const div = document.createElement('div');
div.textContent = message;
output.appendChild(div);
});
// Escribir el mismo comportamiento con versión Creation Function...
// ❌ Versión Creation Function - necesita separar stream principal
import { concat } from 'rxjs';
concat(
clicks$.pipe(
take(3),
mapTo('Clicado')
),
of('Completado')
).subscribe(console.log);Resumen
concat: Óptimo para simplemente combinar múltiples streamsconcatWith: Óptimo cuando se desea agregar procesamiento posterior al stream principal mientras se transforma o procesa
⚠️ Notas Importantes
Retraso Debido a Esperar la Completación
El siguiente Observable no iniciará hasta que el Observable original complete.
ts
import { interval, of } from 'rxjs';
import { concatWith, take } from 'rxjs';
interval(1000).pipe(
take(3), // Completar con 3 valores
concatWith(of('Completo'))
).subscribe(console.log);
// Salida: 0 → 1 → 2 → CompletoManejo de Errores
Si ocurre un error en el Observable anterior, los Observables posteriores no se ejecutarán.
ts
import { throwError, of } from 'rxjs';
import { concatWith, catchError } from 'rxjs';
throwError(() => new Error('Ocurrió un error'))
.pipe(
catchError(err => of('Error recuperado')),
concatWith(of('Siguiente proceso'))
)
.subscribe(console.log);
// Salida: Error recuperado → Siguiente proceso