DEV Community

Cover image for Angular 17/19 | Signals con RxJS usando signalPipeRxjs
Dennys José Márquez Reyes
Dennys José Márquez Reyes

Posted on

2 1

Angular 17/19 | Signals con RxJS usando signalPipeRxjs

👋 ¡Hola Filia! 👋 chicos y chicas. Hoy les hablo de las Signal de Anguar.

✅ Últimamente me he estado actualizando un poco con Angular 17/19, explorando las señales (signals), el nuevo 𝗺𝗼𝗱𝗲𝗹, y todo este mundo moderno de Angular. 🚀 y aunque están en modo solo de desarrollo y no es conveniente aplicarlo en producción son muy interesantes.

✅ Como muchos de nosotros, estoy acostumbrado a usar observables y operadores de RxJS (pipe) para manipular valores o realizar acciones antes de que se dispare un observable.

👉 Entonces, se me ocurrió una idea: 𝗽𝗼𝗿 𝗾𝘂é 𝗻𝗼 𝗵𝗮𝗰𝗲𝗿 𝗹𝗼 𝗺𝗶𝘀𝗺𝗼 𝗰𝗼𝗻 𝘀𝗲ñ𝗮𝗹𝗲𝘀? 🤔

Usando 𝘁𝗼𝗢𝗯𝘀𝗲𝗿𝘃𝗮𝗯𝗹𝗲 (que convierte una señal en un observable), creé una función llamada: 𝘀𝗶𝗴𝗻𝗮𝗹𝗣𝗶𝗽𝗲𝗥𝘅𝗷𝘀 que permite aplicar operadores de RxJS como debounceTime, distinctUntilChanged, map, tap, etc. directamente a una señal. ¡Es como tener lo mejor de ambos mundos! 🌟

📌 Les quise mostrar una de las nuevas características de Angular moderno que es el 𝗺𝗼𝗱𝗲𝗹.

Esta directiva (entrada/salida) es increíblemente útil para comunicar componentes padres e hijos de manera bidireccional.

Combiné model con señales para crear un flujo interactivo y reactivo. 💡

Función signalPipeRxjs

import { Signal } from '@angular/core';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import { OperatorFunction } from 'rxjs';

/**
 * Transforma una señal usando operadores de RxJS.
 *
 * Esta función toma una señal de Angular (`Signal`) y aplica un operadores de RxJS
 * (como `debounceTime`, `distinctUntilChanged`, etc.) para transformar su valor.
 * Luego, devuelve una nueva señal que refleja el resultado de la transformación.
 *
 * @param source La señal original que se desea transformar.
 * @param initialValueOrOperator Un valor inicial opcional o un operador de RxJS que
 * se aplicará a la señal.
 * Si se proporciona un operador, debe ser del tipo `OperatorFunction<[A], R>`,
 * donde `A` es el tipo de la señal original y `R` es el tipo del valor transformado.
 *
 * @returns Una nueva señal (`Signal<R>`) que contiene el valor transformado después
 * de aplicar el operador de RxJS.
 *
 * @example
 * // Aplicar debounceTime y distinctUntilChanged a una señal
 * const searchSignal = signal('');
 * const transformedSignal = signalPipeRxjs(
 *   searchSignal,
 *   pipe(
 *     debounceTime(500),
 *     distinctUntilChanged()
 *   )
 * );
 *
 * // Usar la señal transformada en un effect
 * effect(() => {
 *   console.log('Valor transformado:', transformedSignal());
 * });
 */
export function signalPipeRxjs<R, A>(
  source: Signal<A>,
  initialValueOrOperator?: R | OperatorFunction<[A], R>
): Signal<R> {
  const obs$ = toObservable(source);

  const pipeOperator =
    typeof initialValueOrOperator === 'function'
      ? initialValueOrOperator
      : undefined;

  const result$ = obs$.pipe(
    pipeOperator as unknown as OperatorFunction<any, R>
  );

  return toSignal(result$) as Signal<R>;
}

Enter fullscreen mode Exit fullscreen mode

Componente hijo con model

@Component({
  selector: 'app-search-products',
  template: `
    <input
      [(ngModel)]="searchProduct"
      placeholder="Busca un producto"
    />
  `,
})
export class SearchProductsComponent {
  searchProduct = model.required<string>(); // Señal de entrada/salida
}
Enter fullscreen mode Exit fullscreen mode

Uso en el componente padre

@Component({
  selector: 'app-category-filter',
  template: `
    <app-search-products [(searchProduct)]="searchProduct" />
  `,
})
export class CategoryFilterComponent {
  readonly searchProduct = signal<string>(''); // Señal para el valor de búsqueda

  readonly onSearch = signalPipeRxjs(
    this.searchProduct,
    pipe(
      debounceTime(500), // Debounce de 500ms
      distinctUntilChanged(), // Solo emite si el valor es distinto al anterior
      map(value => value.toUpperCase()), // Convertir a mayúsculas
      tap(value => console.log('Valor transformado:', value)) // Loggear el valor
    )
  );

  constructor() {
    effect(() => {
      const query = this.onSearch(); // Obtenemos el valor transformado
      console.log('Buscando:', query);
      // Aquí puedes llamar a un servicio para realizar la búsqueda
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

📢 Por favor si lo encuentra útill comparte 👍 👍 👍


¿Por qué es útil?

  1. Flexibilidad: Puedes aplicar cualquier operador de RxJS a una señal.

  2. Reactividad: Las señales y model hacen que la comunicación entre componentes sea más intuitiva y eficiente.

  3. Código limpio: La función signalPipeRxjs encapsula la lógica de transformación, haciendo que el código sea más modular y fácil de mantener.

Qué te parece?

Has probado las señales y model en Angular? ¿Qué otros casos de uso se te ocurren para esta función? ¡Comparte tus ideas en los comentarios! 👇

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

👋 Kindness is contagious

If you found this post useful, consider leaving a ❤️ or a nice comment!

Got it