
RxJs: Operadores básicos
- Dacadev
- Programación , Typescript
- 1 de junio de 2026
Tabla de Contenido
En una aplicación reactiva, el problema rara vez consiste solo en recibir eventos; el reto aparece al transformar, filtrar y acumular emisiones sin acoplar la lógica de negocio a cada suscripción. Los operadores rxjs resuelven ese punto al permitir que un Observable exponga un flujo derivado, más específico y más seguro para quien lo consume.
Note
Este artículo forma parte de la serie RxJs. Venimos de analizar Funciones para crear Observables y aquí abordaremos pipe, map, pluck, mapTo, filter, encadenamiento, tap, reduce y scan. El siguiente paso será estudiar Operadores de tiempo.
Qué son los operadores rxjs y cómo funciona pipe
Un operador es una función que recibe las emisiones de un Observable y devuelve otro Observable con un comportamiento derivado. Ese comportamiento puede transformar valores, filtrar emisiones, ejecutar efectos secundarios controlados o acumular estado a lo largo del stream.
En RxJS, los operadores se aplican con el método pipe. Esta API permite declarar una cadena de procesamiento explícita, donde cada operador recibe la salida del anterior y entrega su resultado al siguiente. La documentación oficial de RxJS describe este patrón como la base para componer flujos asincrónicos de manera declarativa.
import { range, map } from 'rxjs';
range(1, 5)
.pipe(
map((value) => value * 10)
)
.subscribe(console.log);
La suscripción no debería cargar con toda la lógica de transformación. En un diseño mantenible, subscribe se reserva para consumir el resultado final, mientras que pipe documenta la estrategia de procesamiento del flujo.
Tip
Mantén la lógica de transformación dentro de pipe. Esto reduce acoplamiento, facilita pruebas unitarias sobre operadores y evita suscripciones con demasiada responsabilidad.
map: transformar emisiones con map filter rxjs
El operador map proyecta cada emisión del Observable original hacia un nuevo valor. Es equivalente al concepto de proyección funcional: por cada entrada existe una salida, sin modificar el origen y sin alterar la cantidad de emisiones.

En el diagrama de canicas, cada valor emitido pasa por una función de transformación y continúa en el stream resultante con una nueva representación. Esto permite convertir modelos, normalizar datos o derivar valores calculados antes de que lleguen a la suscripción.
import { range, map } from 'rxjs';
const source$ = range(1, 5);
const multiplied$ = source$.pipe(
map<number, number>((value) => value * 10)
);
source$.subscribe((value) => console.log('source', value));
multiplied$.subscribe((value) => console.log('map', value));
También puedes escribirlo de forma compacta cuando no necesitas conservar una referencia al Observable intermedio:
import { range, map } from 'rxjs';
range(1, 5)
.pipe(
map<number, number>((value) => value * 10)
)
.subscribe(console.log);
El resultado conserva el orden de las emisiones y aplica la función a cada valor individual.

map es una pieza central en pipelines donde necesitas convertir eventos del DOM, respuestas HTTP o estructuras de dominio en datos listos para la capa que los consume.
pluck: extraer propiedades anidadas
El operador pluck extrae propiedades de objetos emitidos por un Observable. En notas y proyectos con RxJS más antiguos aparece con frecuencia para leer rutas como target.baseURI dentro de un evento del DOM.

Un ejemplo clásico consiste en escuchar eventos keyup y extraer una propiedad anidada del objeto KeyboardEvent.
import { fromEvent } from 'rxjs';
import { pluck } from 'rxjs/operators';
const keyup$ = fromEvent<KeyboardEvent>(document, 'keyup');
const baseUri$ = keyup$.pipe(
pluck('target', 'baseURI')
);
keyup$.subscribe(console.log);
baseUri$.subscribe((value) => console.log('pluck', value));
Al presionar una tecla, el Observable original emite el evento completo, mientras que el stream derivado emite solo la propiedad seleccionada.

Warning
pluck está deprecado en versiones modernas de RxJS. Prefiere map con acceso explícito a propiedades, porque ofrece mejor tipado, autocompletado y refactorización segura.
La alternativa recomendada usa map:
import { fromEvent, map } from 'rxjs';
const keyup$ = fromEvent<KeyboardEvent>(document, 'keyup');
const baseUri$ = keyup$.pipe(
map((event) => event.target instanceof Element ? event.target.baseURI : '')
);
baseUri$.subscribe((value) => console.log('baseURI', value));
mapTo: reemplazar cada emisión por un valor constante
El operador mapTo transforma cualquier entrada en un valor constante. Es útil cuando el contenido de la emisión original no importa y solo necesitas representar una señal semántica, por ejemplo: “hubo interacción”, “se presionó una tecla” o “se completó un paso”.

import { fromEvent, mapTo } from 'rxjs';
const keyup$ = fromEvent<KeyboardEvent>(document, 'keyup');
const keyPressed$ = keyup$.pipe(
mapTo('Tecla presionada')
);
keyPressed$.subscribe((value) => console.log('mapTo', value));
Cada evento keyup se reemplaza por la misma cadena.

En código moderno, también puedes usar map(() => valor) para lograr el mismo resultado y mantener consistencia con el resto de tus transformaciones.
filter: filtrar emisiones del stream
El operador filter deja pasar únicamente las emisiones que cumplen un predicado. A diferencia de map, este operador sí puede cambiar la cantidad de emisiones que recibe la suscripción, porque descarta los valores que no satisfacen la condición.

El callback de filter recibe el valor emitido y el índice de la emisión. Esto permite aplicar reglas basadas en el contenido del valor o en su posición dentro del stream.
import { range, filter } from 'rxjs';
range(10, 20)
.pipe(
filter((value, index) => {
console.log('index', index);
return value % 2 === 1;
})
)
.subscribe(console.log);
En este caso, solo pasan los números impares.

filter también es útil con objetos de dominio. El siguiente ejemplo convierte un arreglo en un Observable y deja pasar únicamente los personajes de tipo heroe.
import { from, filter } from 'rxjs';
interface Personaje {
tipo: 'heroe' | 'villano';
nombre: string;
}
const personajes: Personaje[] = [
{ tipo: 'heroe', nombre: 'Batman' },
{ tipo: 'heroe', nombre: 'Robin' },
{ tipo: 'villano', nombre: 'Joker' }
];
from(personajes)
.pipe(
filter((personaje) => personaje.tipo === 'heroe')
)
.subscribe((personaje) => console.log('filter', personaje));
El stream resultante contiene solo los objetos que cumplen el predicado.

Info
filter no transforma el valor; controla si una emisión continúa o se descarta. Si necesitas modificar el valor después del filtrado, encadena un map a continuación.
Encadenamiento con el operador pipe rxjs
El operador pipe rxjs permite componer varios operadores en una secuencia legible. Cada paso debería tener una responsabilidad clara: extraer un dato, filtrar una condición, transformar un modelo o ejecutar un efecto secundario controlado.
import { fromEvent, filter, map } from 'rxjs';
const enterKey$ = fromEvent<KeyboardEvent>(document, 'keyup').pipe(
map((event) => event.code),
filter((keyCode) => keyCode === 'Enter')
);
enterKey$.subscribe((keyCode) => console.log('chain', keyCode));
Aquí el primer operador transforma KeyboardEvent en event.code, y el segundo operador deja pasar únicamente la tecla Enter. La suscripción recibe un stream ya procesado, sin conocer el detalle del evento original del DOM.
Esta composición es especialmente importante cuando el flujo crece. En lugar de acumular lógica imperativa dentro de callbacks, puedes expresar el pipeline de datos como una serie de transformaciones pequeñas y verificables.
tap: efectos secundarios sin transformar el flujo
El operador tap ejecuta efectos secundarios sobre las emisiones, pero no modifica el valor que atraviesa el pipeline. Es una herramienta frecuente para depuración, métricas, logging o integración con observabilidad, siempre que no se use para alterar estado crítico de forma opaca.

import { range, map, tap } from 'rxjs';
const numbers$ = range(1, 5);
numbers$
.pipe(
tap((value) => console.log('tap-antes', value)),
map((value) => value * 10),
tap({
next: (value) => console.log('tap-despues', value),
complete: () => console.log('se terminó todo')
})
)
.subscribe((value) => console.log('subs', value));
tap acepta un callback para next o un objeto con handlers como next, error y complete. En ambos casos, el valor que continúa hacia el siguiente operador permanece intacto.

Warning
Evita usar tap como sustituto de map o filter. Si un paso cambia el valor del stream, debe expresarse con un operador de transformación explícito.
reduce: acumular y emitir al completar
El operador reduce aplica una función acumuladora sobre las emisiones y entrega un único resultado cuando el Observable completa. Su comportamiento es apropiado para streams finitos, como arreglos, rangos o secuencias delimitadas con operadores como take.

import { interval, reduce, take, tap } from 'rxjs';
const totalReducer = (accumulator: number, current: number) => {
return accumulator + current;
};
interval(500)
.pipe(
take(6),
tap(console.log),
reduce(totalReducer, 5)
)
.subscribe({
next: (value) => console.log('next', value),
complete: () => console.log('complete')
});
El segundo argumento de reduce define el valor inicial del acumulador. En el ejemplo, el acumulador inicia en 5, procesa seis emisiones del interval y emite el total solo cuando take(6) completa el flujo.

Warning
reduce necesita que el Observable complete para emitir el resultado. En streams infinitos o de larga vida, como eventos del DOM sin delimitador, no recibirás salida si no aplicas un operador que cierre la secuencia.
scan: acumular y emitir cada estado intermedio
El operador scan también aplica una función acumuladora, pero emite cada estado intermedio del acumulador. Esta diferencia lo convierte en una pieza clave para modelar estado reactivo: contadores, historial de acciones, progreso incremental o snapshots derivados de eventos.

Comparar reduce y scan sobre la misma secuencia deja clara la diferencia operativa.
import { from, reduce, scan } from 'rxjs';
const numbers = [1, 2, 3, 4, 5];
const totalAccumulator = (accumulator: number, current: number) => {
return accumulator + current;
};
from(numbers)
.pipe(
reduce(totalAccumulator, 0)
)
.subscribe((value) => console.log('reduce', value));
from(numbers)
.pipe(
scan(totalAccumulator, 0)
)
.subscribe((value) => console.log('scan', value));
reduce emite solo el total final. scan emite 1, 3, 6, 10 y 15, porque cada emisión representa el nuevo estado acumulado.

En arquitecturas reactivas, scan suele ser más útil que reduce cuando el consumidor necesita reaccionar a cada transición de estado, no solo al resultado final.
Conclusión
Los operadores básicos de RxJS permiten convertir streams genéricos en flujos especializados y mantenibles. map transforma, filter controla qué continúa, tap observa sin modificar, reduce acumula hasta completar y scan expone cada estado acumulado.
Dominar pipe y estos operadores establece una base sólida para operadores más avanzados. En el siguiente artículo de la serie abordaremos operadores de tiempo, donde la latencia, la frecuencia de emisión y la coordinación temporal pasan a ser parte central del diseño reactivo.

