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í:
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.
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() //3000Cuando 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() // 3000Update:
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(); //3100mutate:
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

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.
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í:
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
Modern frontend development — Angular, React, TypeScript, accessibility, and performance.
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
Angular 19 y ZoneLess
No importa si usas Angular 14, 15 o 19: tus aplicaciones siguen bajo el paraguas de Zone.js. Aunque sientas que funcionan bien, por debajo Angular necesita recorrer todo el árbol del DOM con Zone.js, lo que afecta el rendimiento.
Cómo simplificar la comunicación entre componentes en Angular con model() y Signals
Estuve muy ocupado en septiembre, pero ahora estoy preparando un taller sobre signals y explorando ideas sobre cómo pueden simplificar nuestro código. Recientemente usaba @Input() y @Output para la comunicación entre componentes, pero Angular 17 introdujo alternativas más limpias...
Entendiendo InjectionContext y los effects de Signals
Ayer estuve conversando con mi amigo Jörgen de Groot sobre Signals. Cuando la conversación pasó a los effects, se encontró con el error ERROR NG0203. Poco después, otro amigo me contactó con el mismo problema. Decidí escribir un breve artículo para explicar por qué ocurre...
Guía de Content Projection en Angular: paso a paso
Uno de los platos más sencillos de preparar es una hamburguesa. Aunque la base siempre es la misma, los ingredientes pueden variar: te muestro cómo usar ng-content en Angular para proyectar contenido en áreas flexibles y trabajar con múltiples slots.
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.
Únete a 13,800+ desarrolladores y lectores.
Cero spam. Cancela tu suscripción en cualquier momento.