🇺🇸 EN
Frontend
·8 min read·↗·🇺🇸 EN

Primeros pasos con Angular Signals: Una introducción básica

Portada principal del artículo: Primeros pasos con Angular Signals: Una introducción básica

Desde Angular 16 he escuchado hablar mucho sobre Signals. El equipo de @angular/team hizo un gran trabajo escuchando a la comunidad y mejorando la API de Signals con la interoperabilidad con Rxjs interop.

Ayer estuve conversando con mi amigo @Juan Berzosa. Él todavía no había probado Signals, así que decidí escribir un artículo corto para explicar qué son los signals y cómo ayudan a Angular a tener un mejor sistema reactivo y de detección de cambios.

Antes de entrar en detalle con Signals, preguntémonos: ¿por qué el siguiente código no responde a los cambios?

Tenemos el servicio CardsService, con tres propiedades: cards, cardsLeft, lastClient y el método add. El método add agrega un nuevo valor al arreglo cards, disminuye el valor de cardsLeft y asigna el nombre del titular.

@Injectable({ providedIn: 'root' })
export class CardsService {
  cards: Array<Card> = [
    {
      id: '1',
      holder: 'Dany',
      status: 'Active',
    },
    {
      id: '2',
      holder: 'Edgar',
      status: 'Active',
    },
  ];
 
  cardsleft: number = 3;
  lastClient = 'No clients yet!';
 
  add(holder: string) {
    const card = {
      id: Math.random().toFixed(),
      holder,
      status: 'pending',
    };
    this.cards = [...this.cards, card];
    this.cardsleft--;
    this.lastClient = `Thanks ${holder}!!`;
    console.log(this.cards, this.lastClient, this.cardsleft);
  }
}

En el componente usamos las propiedades de CardsService para mostrar información en la plantilla, mostrando la lista de tarjetas del servicio y los valores de cardLeft y lastClient.

El botón de guardar llama al método add del servicio, el cual inserta un elemento y modifica el arreglo y las propiedades. Pero, ¿por qué no se refleja en mi componente?

 
@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule],
  template: `
   <h3>We have {{cardLeft}} cards </h3>
   <p>{{lastClient}}</p>
   <input type="text" #holder />
   <button (click)="add(holder)">Save</button>
   <div *ngFor="let card of cards">
   {{card.holder}} {{card.id}}
   </div>
  `,
})
export class App {
  private cardsService = inject(CardsService);
 
  cards = this.cardsService.cards;
  cardLeft = this.cardsService.cardsleft;
  lastClient = this.cardsService.lastClient;
 
  add(holder: HTMLInputElement) {
    this.cardsService.add(holder.value);
    holder.value = '';
  }
}

Puedes probarlo aquí:

Link

El servicio modifica las propiedades, pero esos cambios no se ven reflejados en mi componente. Estas propiedades no le avisan a Angular que han cambiado. Algunas soluciones incluyen:

  • Reasignar los valores.

  • Usar un enfoque reactivo con RxJS.

¿Y si te dijera que podemos escribir código reactivo de verdad que reaccione a los cambios fácilmente, sin RxJS y sin necesidad de reasignar valores?

Reactivity, Change Detection, and NgZone

¿Sabes cómo detecta los cambios Angular? Angular escucha eventos o modificaciones de datos en la app ejecutando la detección de cambios en toda la aplicación mediante NgZone.

NgZone facilita la detección de cambios en aplicaciones de Angular, pero cuando la aplicación crece, ZoneJS puede ralentizarla y afectar el rendimiento. De hecho, solemos tomar algunas medidas para resolver este problema:

  • Usar la detección de cambios OnPush.

  • Ejecutar código fuera de Angular con ngZone.runOutsideAngular(() => ...), para que la detección de cambios se ejecute con menos frecuencia y todo funcione más rápido.

Sin embargo, otro efecto secundario es que NgZone no indica qué componente cambió, solo que ocurrió algo en la app. Por lo tanto, Angular debe revisar todos los componentes para identificar cuál fue el que cambió.

Para aprender más sobre la detección de cambios, mira este video de Decoded Frontend.

Link

El equipo de Angular reevaluó cómo funciona la reactividad para ofrecer una base sólida y una excelente solución, teniendo en cuenta estos puntos:

  • Saber exactamente qué parte del estado de la aplicación cambia y cuándo.

  • Permitir a los desarrolladores indicar dónde se usa ese valor y cuándo cambia.

  • Permitir que Angular actualice de forma automática todo lo que dependa de ese valor.

  • Obtener un control granular de las actualizaciones.

Tomando en cuenta estos puntos, el equipo de Angular presentó Angular Signals.

Ya está listo para usarse en developer preview a partir de la versión 16 de Angular.

Signals

Signals incorpora reactividad granular en el núcleo de Angular, ofreciendo una nueva forma de reconocer cuándo han cambiado nuestros datos. Mejoran la detección de cambios de Angular ejecutándola únicamente en el componente donde cambiaron los datos, lo que incrementa la eficiencia de tu aplicación en Angular y hace que nuestro código sea más reactivo.

Bien, pero ¿qué es un Signal? Un signal es un valor acompañado de notificaciones de cambio, que ofrece actualizaciones a quienes lo leen y se puede usar en componentes para leer valores, en servicios, plantillas o en cualquier lugar.

Signals ofrece tres tipos principales, conocidos como primitivas reactivas. Estos bloques de construcción provistos por Angular nos permiten trabajar con Signals de forma efectiva. Los tres tipos principales son: Writable Signals, Computed Signals y Effects.

Writable Signals

Declaramos una variable signal usando la función signals() y le asignamos un valor inicial. Esta guarda el valor y le notifica a Angular cuando ocurre un cambio.

quantity = signal<number>(3000)

Para leer el valor del signal 'quantity', usamos paréntesis () para acceder al dato.

payment() //3000

Cuando creamos una variable usando la función 'signal', se convierte en un signal de escritura (writable) y ofrece métodos para trabajar con ella, como 'set', 'update' o 'mutate'.

Set:

El método set reemplaza el valor de un signal por uno nuevo. Al actualizarse el signal, el código notifica a los consumidores que el valor cambió.

   payment.set(3000);
   payment() // 3000

Update:

El método update() modifica el signal tomando su valor actual. Le pasas una función simple al método update. Esta función recibe el valor actual del signal para que lo modifiques como necesites.

typescript payment.update(p => p + 100) payment(); //3100

mutate:

Gracias a @Rainer Hahnekamp, me enteré de que el equipo de Angular marcó Signals como estable para la versión 17 y eliminó el método mutate de la API pública.

Mi truco secreto para mantenerme al día con lo que pasa en el mundo de Angular es ngnews en YouTube y Twitter (x).

https://github.com/angular/angular/commit/c7ff9dff2c14aba70e92b9e216a2d4d97d6ef71e

![Image](https://pbs.twimg.com/media/F7xRr7GX0AAAstD?format=jpg&name=medium align="left")

El método mutate() modifica el contenido del valor de un signal, no el valor del signal en sí. Úsalo con arreglos para modificar elementos del arreglo, y con objetos para modificar propiedades del objeto.

Los consumidores no son notificados sobre el cambio del signal, deben leer el nuevo valor del signal.
demo = signal<any>({ id: 1 }); 
demo.mutate(p => { id: 2 })

Computed Signals:

Un computed signal es una variable que refleja los cambios entre signals y lee los signals de los que depende, y solo cambia si cambian los valores de los que depende. Además, solo se calcula una vez aunque se utilice en tres lugares diferentes.

Los Computed Signals son de solo lectura, no se pueden modificar con 'set', 'update' ni 'mutate'.

soldOut = computed(() => this.cardSlots() <= 0);

Effect

Los effects son funciones que se ejecutan cuando cambian los valores de los signals de los que dependen. Podemos indicarle a Angular que ejecute una función que use valores de signals, y Angular volverá a ejecutar el código automáticamente cuando cambien los signals asociados.

 effect(() => {
      if (this.soldOut()) {
        this.title = 'Bank Closed';
      }
  });

Refactorizando con Signals

Ya sabemos qué es un signal, así como los tres tipos: WritableSignal, Computed y effect. Con este conocimiento, podemos refactorizar nuestro código.

Primero, convertimos el arreglo cards en un signal de tarjetas, y convertimos cardSlots y lastClient en signals de escritura.

  cards = signal<Card[]>([
    {
      id: '1',
      holder: 'Dany',
      status: 'Active',
    },
    {
      id: '2',
      holder: 'Edgar',
      status: 'Active',
    },
  ]);
 
 
  cardSlots = signal<number>(3);
  lastClient = signal('No clients yet!');

A continuación, declaramos un computed para reaccionar a los cambios de cardSlots.

 soldOut = computed(() => this.cardSlots() <= 0);

En el método add, actualizamos las propiedades de los signals con los métodos set y update.

  add(holder: string) {
    const card = {
      id: Math.random().toFixed(),
      holder,
      status: 'pending',
    };
    this.cards.update((p) => [...p, card]);
    this.cardSlots.update((p) => p - 1);
    this.lastClient.set(`Thanks ${holder}`!!);
  }

En el componente, agregamos una referencia a esas variables.

 cards = this.cardsService.cards;
  cardSlots = this.cardsService.cardSlots;
  soldOut = this.cardsService.soldOut;
  lastClient = this.cardsService.lastClient;

En el código HTML, accedemos al valor del signal usando paréntesis ().

    <h1>{{title}}</h1>
    <h3>We have {{cardSlots()}} slots </h3>
    <p>{{lastClient()}}</p>
    <input type="text" #holder [disabled]="soldOut()"/>
    <button (click)="add(holder)"
     [disabled]="soldOut()">Save</button>
 
    <div *ngFor="let card of cards()">
    {{card.holder}} {{card.id}}
    </div>

Finalmente, usamos el effect en el constructor, leyendo el signal 'soldOut' para actualizar el título:

  constructor() {
    effect(() => {
      if (this.soldOut()) {
        this.title = 'Bank Closed';
      }
    });
  }

El código final se ve así:

Link

Conclusión

¡Listo! Ya tenemos un panorama básico de cómo hacer que tus aplicaciones sean reactivas usando Signals. Signals representa un paso fundamental en Angular al traer reactividad granular, eficiencia y mejoras de rendimiento a nuestras aplicaciones.

¿Pero qué pasa con RxJS? ¿Cómo impacta Signals en él? Hablaré de esto en otro artículo. Mientras tanto, te recomiendo revisar la documentación oficial para conocer más al respecto.

Happy coding!

Photo by Carlos Alberto Gómez Iñiguez on Unsplash

Parte de la serie Frontend

Modern frontend development — Angular, React, TypeScript, accessibility, and performance.

Ver serie completa

Preguntas Frecuentes

¿Qué son los Signals en Angular y por qué se introdujeron?

Los Signals en Angular son una primitiva reactiva introducida en Angular 16 que permite al framework rastrear con exactitud qué partes de la aplicación dependen de un estado determinado. Se introdujeron para brindarle a Angular un mecanismo de detección de cambios más eficiente, evitando la necesidad de revisar todo el árbol de componentes en cada evento como hace la detección basada en Zone.js.

¿Cuál es la diferencia entre un signal, un computed signal y un effect en Angular?

Un signal es un valor reactivo de escritura creado con la función signal. Un computed signal es un valor derivado de solo lectura que se recalcula automáticamente cuando cambian sus signals de origen, creado con la función computed. Un effect es una función para efectos secundarios que se ejecuta cada vez que cambian los signals que lee en su interior, creado con la función effect.

¿Cómo se actualiza el valor de un signal en Angular?

Puedes actualizar un signal usando tres métodos: set, que reemplaza el valor actual por completo con uno nuevo; update, que toma el valor actual como argumento y devuelve el nuevo valor mediante una función callback; y mutate, que se usaba para mutar objetos o arreglos directamente sin reemplazar la referencia.

¿Por qué Angular no detecta los cambios cuando modificas directamente la propiedad de un servicio?

Cuando usas la detección de cambios basada en Zone.js, Angular necesita un evento o un ciclo de vida para disparar una verificación. Si una propiedad de un servicio cambia fuera de la plantilla o del ciclo de vida del propio componente, es posible que Angular no sepa que debe volver a renderizar. Los Signals solucionan esto haciendo explícitas las dependencias, de modo que Angular solo vuelve a renderizar exactamente lo que necesita cambiar.

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.