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

1 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! 👇

Image of Stellar post

From Hackathon to Funded - Stellar Dev Diaries Ep. 1 🎥

Ever wondered what it takes to go from idea to funding? In episode 1 of the Stellar Dev Diaries, we hear how the Freelii team did just that. Check it out and follow along to see the rest of their dev journey!

Watch the video

Top comments (0)

Image of Stellar post

Check out Episode 1: How a Hackathon Project Became a Web3 Startup 🚀

Ever wondered what it takes to build a web3 startup from scratch? In the Stellar Dev Diaries series, we follow the journey of a team of developers building on the Stellar Network as they go from hackathon win to getting funded and launching on mainnet.

Read more

👋 Kindness is contagious

Dive into this insightful write-up, celebrated within the collaborative DEV Community. Developers at any stage are invited to contribute and elevate our shared skills.

A simple "thank you" can boost someone’s spirits—leave your kudos in the comments!

On DEV, exchanging ideas fuels progress and deepens our connections. If this post helped you, a brief note of thanks goes a long way.

Okay