🇺🇸 EN
Angular
·5 min read·↗·🇺🇸 EN

Cómo combinar Async Pipes en Angular: De combineLatest a Signals y @let

Portada principal del artículo: Cómo combinar Async Pipes en Angular: De combineLatest a Signals y @let

⚡ ¿Usas Angular Moderno (v17+)? Salta directamente a 2. El enfoque moderno: Convertir flujos con toSignal() para eliminar el pipe async por completo.

Si estás construyendo una aplicación en Angular que obtiene datos de varias APIs, es muy probable que hayas escrito una plantilla como esta:

<!-- ⚠️ El problema: Múltiples async pipes que provocan peticiones duplicadas -->
<div *ngIf="user$ | async as user">
  <h2>{{ (user$ | async)?.name }}</h2>
  
  <div *ngIf="stats$ | async as stats">
    <p>Points: {{ (stats$ | async)?.points }}</p>
  </div>
</div>

Este es un error común y costoso. Si user$ representa una petición HTTP, usar | async varias veces dispara múltiples peticiones de red idénticas. Para empeorar las cosas, anidar directivas estructurales *ngIf genera un HTML profundo y difícil de leer.

Aquí tienes un resumen rápido de cómo solucionaremos este problema usando patrones modernos de Angular:

PatrónIdeal para¿Requiere Async Pipe?Peticiones de red
combineLatest (ViewModel)Pipelines de datos complejos en RxJSSí (Solo 1 en la raíz)1
Signals (toSignal)Aplicaciones modernas de Angular ZonelessNo (Lectura directa del valor)1
Sintaxis @let (Angular 18+)Alias rápidos de variables en la plantillaSí1

Vamos a revisar las tres formas más limpias de combinar flujos y eliminar suscripciones duplicadas.


1. El patrón clásico de RxJS: ViewModel con combineLatest 🔄

Si tu proyecto depende en gran medida de flujos con RxJS, el patrón más sólido es combinar todos los observables independientes en un único flujo vm$ (ViewModel) dentro de la clase de tu componente:

import { Component, inject } from '@angular/core';
import { combineLatest, Observable } from 'rxjs';
import { PlayerService, Player, PlayerStats } from './player.service';
 
interface PlayerViewModel {
  player: Player;
  stats: PlayerStats;
}
 
@Component({
  selector: 'app-player-profile',
  templateUrl: './player-profile.component.html'
})
export class PlayerProfileComponent {
  private playerService = inject(PlayerService);
  private playerId = 23;
 
  player$: Observable<Player> = this.playerService.getPlayer(this.playerId);
  stats$: Observable<PlayerStats> = this.playerService.getStats(this.playerId);
 
  // Combina múltiples flujos en un único observable ViewModel
  vm$: Observable<PlayerViewModel> = combineLatest({
    player: this.player$,
    stats: this.stats$
  });
}

En la plantilla (Flujo de control moderno)

Con el bloque @if del Angular moderno, solo necesitas un único pipe async en la raíz de la vista. Una vez desenvuelto, accedes a todas las propiedades de manera síncrona:

@if (vm$ | async; as vm) {
  <div class="player-card">
    <h2>{{ vm.player.name }}</h2>
    <p>Position: {{ vm.player.position }}</p>
 
    <ul>
      <li>Points: {{ vm.stats.points }}</li>
      <li>Rebounds: {{ vm.stats.rebounds }}</li>
    </ul>
  </div>
} @else {
  <div class="skeleton-loader">Loading player profile...</div>
}

Ahora tenemos exactamente una sola suscripción y cero peticiones de red duplicadas.

A continuación, veamos cómo los Signals en Angular moderno hacen este proceso todavía más simple.


2. El enfoque moderno: Convertir flujos con toSignal() ⚡

Con los Signals de Angular, puedes eliminar el pipe async por completo. El paquete @angular/core/rxjs-interop ofrece la función toSignal(), que se suscribe al Observable internamente y lo expone como un Signal reactivo y síncrono:

import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { PlayerService } from './player.service';
 
@Component({
  selector: 'app-player-signals',
  standalone: true,
  template: `
    @if (player(); as p) {
      <div class="player-card">
        <h2>{{ p.name }}</h2>
        
        @if (stats(); as s) {
          <p>Points: {{ s.points }} | Rebounds: {{ s.rebounds }}</p>
        }
      </div>
    } @else {
      <p>Loading player...</p>
    }
  `
})
export class PlayerSignalsComponent {
  private playerService = inject(PlayerService);
  private playerId = 23;
 
  // Gestiona automáticamente el ciclo de vida de suscripción y cancelación
  player = toSignal(this.playerService.getPlayer(this.playerId));
  stats = toSignal(this.playerService.getStats(this.playerId));
}

Por qué toSignal() es el estándar recomendado:

  • Sin cancelaciones manuales de suscripción: Las suscripciones están vinculadas directamente al ciclo de vida del componente y se limpian automáticamente.
  • Lectura síncrona en la plantilla: Lees los valores con llamadas a funciones normales (player()). No necesitas ningún pipe async.
  • Reactividad granular: Las actualizaciones solo disparan las vinculaciones del DOM necesarias, preparando tu aplicación para la detección de cambios Zoneless.

Ahora revisemos la incorporación más reciente en Angular 18.1: la declaración @let.


3. La solución en Angular 18.1+: Variables de plantilla con @let 🎯

A partir de Angular 18.1, puedes declarar variables locales directamente dentro de la plantilla usando @let. Esto elimina la necesidad de crear elementos contenedores ficticios solo para asignarle un alias al pipe async:

@let player = player$ | async;
@let stats = stats$ | async;
 
@if (player && stats) {
  <div class="player-container">
    <h2>{{ player.name }}</h2>
    <p>Points: {{ stats.points }}</p>
    <p>Assists: {{ stats.assists }}</p>
  </div>
}

Beneficios clave de @let:

  • Funciona en cualquier parte del ámbito de la plantilla.
  • Ofrece inferencia de tipos estricta para las expresiones secundarias.
  • Evita crear contenedores ng-container adicionales en el DOM solo para desenvolver un observable.

Resumen

Para mantener tus aplicaciones de Angular rápidas y limpias:

  1. Nunca repitas pipes async sobre el mismo observable en diferentes elementos de la plantilla. Provoca peticiones de red duplicadas.
  2. Usa combineLatest para construir un ViewModel vm$ unificado cuando trabajes con flujos complejos de RxJS.
  3. Adopta toSignal() en componentes modernos para beneficiarte de la lectura síncrona en plantillas y de la reactividad de Signals.
  4. Usa @let en Angular 18+ para simplificar el alcance de variables en la plantilla sin anidar elementos ng-container.

Si quieres conocer más técnicas de arquitectura en Angular moderno, revisa mis guías sobre Preguntas esenciales para entrevistas de Angular y Cómo compartir datos entre componentes en Angular.

Parte de la serie Angular

These are my experiences learning and facing my daily challenges working with Angular.

Ver serie completa

Preguntas Frecuentes

¿Por qué es malo usar múltiples async pipes para el mismo observable en Angular?

Cada async pipe aplicado a un observable crea una suscripción independiente. Si el observable es una petición HTTP o un cálculo sin caché, múltiples async pipes provocan peticiones de red duplicadas y ciclos innecesarios de detección de cambios.

¿Cómo reemplazan los Signals de Angular (toSignal) al async pipe?

La función toSignal() de @angular/core/rxjs-interop convierte un observable de RxJS en un Signal. En la plantilla, lees el valor de forma síncrona con una llamada a función data(), eliminando por completo el async pipe.

¿Qué es la sintaxis @let en Angular 18.1+?

La sintaxis @let te permite declarar variables locales en la plantilla (@let user = user$ | async) sin envolver tu HTML en elementos contenedores innecesarios o directivas estructurales vacías.

¿Cómo se combinan múltiples observables en un solo async pipe en Angular clásico?

Puedes usar el operador combineLatest de RxJS en la clase del componente para agrupar múltiples flujos en un único observable vm$ (ViewModel), desenvolviéndolo una sola vez en la plantilla con *ngIf='vm$ | async as vm'.

Artículos Relacionados

Compartir este artículo

Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.


Software real. Lecciones reales.

Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.

User avatar
User avatar
User avatar
User avatar
+13K

Únete a 13,800+ desarrolladores y lectores.

Cero spam. Cancela tu suscripción en cualquier momento.