Angular
Tutorial Angular: Signals en Angular
Angular signals: definición y uso. Aprende a usar signals en Angular con ejemplos prácticos y detallados.
Aprende Angular GRATIS y certifícate¿Qué son los Signals?
Los Signals en Angular representan una abstracción para gestionar la reactividad en las aplicaciones. A diferencia de otros paradigmas reactivos, los Signals proporcionan un enfoque declarativo y estructurado, enfocado en la propagación y sincronización de datos.
Una Signal es una entidad reactiva dentro de Angular que encapsula un valor (actuando como un contenedor para dicho valor) y notifica automáticamente a los consumidores cada vez que ese valor cambia.
Puedes pensar en las Signals de Angular como una combinación de un valor de datos y un mecanismo de notificación de cambios, ofreciendo una manera simplificada de rastrear cambios y actualizar de manera fluida la interfaz de usuario en respuesta a esos cambios.
Características principales de los Signals:
- Comportamiento predecible: Los Signals siguen reglas estrictas para emitir cambios, lo que ayuda a evitar efectos secundarios inesperados.
- Inmutabilidad controlada: Aunque permiten mutaciones, estas son controladas y explícitas, promoviendo una gestión más clara del estado.
- Facilidad de composición: Los Signals pueden ser combinados y derivados fácilmente, permitiendo crear relaciones complejas de manera sencilla.
- Optimización del rendimiento: Al ser declarativos y al permitir la reutilización lógica, los Signals pueden ayudar a minimizar los cálculos redundantes y las actualizaciones innecesarias de la UI.
Ejemplo básico de uso de un Signal
Ponemos un ejemplo donde creamos un Signal que almacena un valor numérico y reaccionamos a los cambios de ese valor.
import { signal, effect } from '@angular/core';
// Creación de un Signal writable
const count = signal(0);
// Subscribirse a los cambios del Signal
effect(() => {
console.log('El valor actual de count es', count());
});
// Mutar el valor del Signal
count.set(1); // Consola: El valor actual de count es 1
count.update(value => value + 1); // Consola: El valor actual de count es 2
En este ejemplo, count
es un Signal que inicialmente tiene un valor de 0
. Cada vez que el valor de count
cambia a través de set
o update
, la función pasada a effect
se ejecuta, reflejando el nuevo valor en la consola.
Signals en Angular proporciona una base sólida para crear aplicaciones reactivas, garantizando que los cambios en los datos se gestionen eficientemente y con un comportamiento predecible.
Signals writables cómo crearlos y usarlos
Un Signal writable es un tipo de Signal en Angular que permite no solo leer su valor actual, sino también modificarlo explícitamente. Estos Signals son fundamentales para gestionar el estado mutable en aplicaciones reactivas, permitiendo que los cambios en el estado se propaguen automáticamente a los consumidores correspondientes.
Para crear un Signal writable, utilizamos la función signal
del paquete @angular/core
. La función signal
acepta un valor inicial y devuelve un objeto Signal que proporciona métodos para leer y actualizar su valor.
Creación de un Signal writable
Aquí mostramos cómo crear un Signal writable con un valor inicial de 0
:
import { signal } from '@angular/core';
// Creación de un Signal writable con valor inicial 0
const count = signal(0);
Métodos para gestionar el estado de un Signal writable
Los Signals writables proporcionan varios métodos para interactuar con el estado:
set(newValue: T)
: Establece el valor del Signal anewValue
, desencadenando cualquier efecto reactivo asociado.update(updateFn: (currentValue: T) => T)
: Actualiza el valor utilizando una función que recibe el valor actual y devuelve el nuevo valor.valueOf()
: Retorna el valor actual del Signal.
Ejemplos prácticos
Establecer un nuevo valor
Utilizamos el método set
para modificar directamente el valor del Signal:
// Establecer el valor a 10
count.set(10);
console.log(count.valueOf()); // Output: 10
Actualizar el valor basado en su estado actual
Con update
, actualizamos el valor del Signal basándonos en su valor actual:
// Incrementar el valor en 1
count.update(currentValue => currentValue + 1);
console.log(count.valueOf()); // Output: 11
Integración con efectos reactivos
Para responder a los cambios en el valor de un Signal writable, utilizamos la función effect
del paquete @angular/core
. Esto nos permite definir comportamientos que se ejecutan automáticamente cuando cambia el valor del Signal:
import { effect } from '@angular/core';
// Crear un efecto que reacciona a los cambios en count
effect(() => {
console.log('El valor de count ha cambiado a', count.valueOf());
});
// Modificar el valor del Signal para activar el efecto
count.set(20); // Consola: El valor de count ha cambiado a 20
count.update(value => value * 2); // Consola: El valor de count ha cambiado a 40
En este ejemplo, cada cambio en el valor de count
desencadena el efecto que registra el nuevo valor en la consola.
Signals computed cómo crearlos y usarlos
Computed Signals en Angular son una abstracción poderosa que permite crear Signals derivados cuyo valor es computado automáticamente a partir de otros Signals. Estos Signals no reciben valores directamente, sino que su valor es el resultado de una función computada que depende de otros Signals.
Para crear un Signal computed, utilizamos la función computed
del paquete @angular/core
. Esta función acepta un callback que define cómo computar el valor del Signal a partir de otros Signals.
Creación de un Signal computed
Veamos cómo crear un Signal computed que dependa de un Signal writable existente:
import { signal, computed } from '@angular/core';
// Creación de un Signal writable
const baseValue = signal(5);
// Creación de un Signal computed que depende de baseValue
const doubleValue = computed(() => baseValue.valueOf() * 2);
console.log(doubleValue.valueOf()); // Output: 10
En este ejemplo, doubleValue
es un Signal computed cuyo valor es siempre el doble del valor de baseValue
. Cada vez que baseValue
cambie, doubleValue
se actualizará automáticamente para reflejar la nueva composición.
Reactividad de los Signals computed
Los Signals computed son reactivos por naturaleza. Esto significa que cualquier efecto o Signal computado que dependa de ellos se actualizará automáticamente cuando sus dependencias cambien:
import { effect } from '@angular/core';
// Crear un efecto que reacciona a cambios en doubleValue
effect(() => {
console.log('El valor de doubleValue es', doubleValue.valueOf());
});
// Modificar el valor del Signal writable baseValue
baseValue.set(10); // Consola: El valor de doubleValue es 20
En este ejemplo, el efecto se ejecutará cada vez que baseValue
cambie, reflexionando el nuevo valor en doubleValue
.
Uso avanzado de Signals computed
Los Signals computed pueden depender de múltiples Signals y contener lógica compleja para computar sus valores:
const multiplier = signal(3);
// Computar un valor que depende de `baseValue` y `multiplier`
const computedValue = computed(() => baseValue.valueOf() * multiplier.valueOf());
console.log(computedValue.valueOf()); // Output: 30
// Reactividad a múltiples dependencias
effect(() => {
console.log('El valor de computedValue es', computedValue.valueOf());
});
// Cambiar las dependencias
baseValue.set(4); // Consola: El valor de computedValue es 12
multiplier.set(5); // Consola: El valor de computedValue es 20
En este ejemplo, computedValue
depende tanto de baseValue
como de multiplier
. Cada vez que cualquiera de estos Signals cambie, computedValue
se volverá a computar y cualquier efecto asociado se ejecutará.
Optimización y limitaciones
A pesar de su potencia, es importante utilizar Signals computed de manera responsable para evitar complejidades innecesarias y ciclos de dependencias. La reactividad en Signals computed está optimizada para evitar recomputaciones redundantes, pero un uso excesivo o indebido puede llevar a problemas de rendimiento y complejidad en el código.
Ejemplos prácticos
Computar valores derivados
const price = signal(200);
const quantity = signal(3);
// Computar el valor total como un Signal computed
const totalPrice = computed(() => price.valueOf() * quantity.valueOf());
effect(() => {
console.log('El precio total es', totalPrice.valueOf());
});
price.set(250); // Consola: El precio total es 750
quantity.set(4); // Consola: El precio total es 1000
Validez y verificación
Los Signals computed también se pueden usar para validar datos y derivar estados:
const username = signal('user123');
const isValidUsername = computed(() => username.valueOf().length >= 5);
effect(() => {
console.log('¿Es el nombre de usuario válido?', isValidUsername.valueOf());
});
username.set('us'); // Consola: ¿Es el nombre de usuario válido? false
username.set('newUser'); // Consola: ¿Es el nombre de usuario válido? true
En conclusión, los Signals computed son una herramienta eficaz para derivar valores de forma reactiva y optimizada, permitiendo manejar la complejidad de aplicaciones en Angular con mayor claridad y previsibilidad.
Consumir signal en un componente
Para consumir un Signal en un componente de Angular, primero es necesario crear el Signal en un contexto adecuado y luego utilizarlo dentro del componente para que la UI reaccione a los cambios en su valor. Los Signals simplifican la reactividad al permitir que el componente se reactive automáticamente ante los cambios de estado, sin necesidad de suscribirse manualmente a observables ni manejar suscripciones complejas.
Crear un Signal en un servicio
Se recomienda encapsular los Signals en servicios para una mejor separación de responsabilidades y facilitar la prueba y reutilización del código. A continuación, creamos un servicio con un Signal writable:
import { Injectable } from '@angular/core';
import { signal } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CounterService {
// Creación de un Signal writable para manejar el conteo
private _count = signal(0);
get count() {
return this._count;
}
increment() {
this._count.update(value => value + 1);
}
decrement() {
this._count.update(value => value - 1);
}
}
Inyectar el servicio en el componente
Una vez que el servicio está configurado, inyectamos el servicio en el componente donde queremos consumir el Signal y utilizamos esa inyección para obtener y reaccionar a los cambios del Signal:
import { Component, effect } from '@angular/core';
import { CounterService } from './counter.service';
@Component({
selector: 'app-counter',
template: `
<div>
<p>Count: {{ count }}</p>
<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
</div>
`,
styleUrls: ['./counter.component.css']
})
export class CounterComponent {
count: number;
constructor(private counterService: CounterService) {
// Consumir el Signal y actualizar el valor local en el componente
effect(() => {
this.count = this.counterService.count();
});
}
increment() {
this.counterService.increment();
}
decrement() {
this.counterService.decrement();
}
}
Reactividad automática en el template
El uso de Signals permite que los cambios en el valor del Signal se reflejen automáticamente en el template del componente sin necesidad de suscribirse manualmente. En el ejemplo anterior, el elemento {{ count }}
en el template se actualizará automáticamente cuando cambie el Signal count
.
Este enfoque no solo simplifica la lógica reactiva en el componente, sino que también mejora la claridad y la mantenibilidad del código. A medida que el estado definido por los Signals cambia, la UI del componente se re-rendea de manera eficiente y predecible.
Ejemplo completo
A continuación se muestra un ejemplo completo de un servicio y un componente que utiliza Signals para manejar el estado y la reactividad:
counter.service.ts
import { Injectable } from '@angular/core';
import { signal } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CounterService {
private _count = signal(0);
get count() {
return this._count;
}
increment() {
this._count.update(value => value + 1);
}
decrement() {
this._count.update(value => value - 1);
}
}
counter.component.ts
import { Component, effect } from '@angular/core';
import { CounterService } from './counter.service';
@Component({
selector: 'app-counter',
template: `
<div>
<p>Count: {{ count }}</p>
<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
</div>
`,
styleUrls: ['./counter.component.css']
})
export class CounterComponent {
count: number;
constructor(private counterService: CounterService) {
effect(() => {
this.count = this.counterService.count();
});
}
increment() {
this.counterService.increment();
}
decrement() {
this.counterService.decrement();
}
}
Con esta estructura, hemos encapsulado la lógica de manejo del estado en un servicio y la hemos consumido en un componente, aprovechando la reactividad y simplicidad que proporcionan los Signals en Angular.
Compartir signals con servicios
Para compartir Signals a través de distintos componentes en Angular, es recomendable utilizar servicios. Los servicios permiten encapsular la lógica de estado independiente de la UI, facilitando la reutilización y el mantenimiento del código. Aquí explicamos cómo crear un servicio con un Signal y cómo exponerlo para que diferentes componentes puedan consumir y reaccionar a los cambios de manera eficiente.
Creación de un servicio con Signals
Empezamos por crear un servicio en Angular que contendrá nuestro Signal. Este servicio encapsulará tanto el Signal como los métodos necesarios para interactuar con él.
import { Injectable } from '@angular/core';
import { signal } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SharedService {
private _sharedSignal = signal<boolean>(false);
get sharedSignal() {
return this._sharedSignal;
}
toggleSignal() {
this._sharedSignal.update(value => !value);
}
}
En este ejemplo, SharedService
contiene un Signal booleano sharedSignal
inicializado a false
, y un método toggleSignal
que invierte su valor.
Inyectar el servicio en componentes
Para que múltiples componentes puedan compartir el estado encapsulado en el servicio, debemos inyectar el servicio en cada componente que desee consumir el Signal.
Componente A
import { Component, effect } from '@angular/core';
import { SharedService } from './shared.service';
@Component({
selector: 'app-component-a',
template: `
<div>
<p>Componente A - Estado: {{ state }}</p>
<button (click)="toggleState()">Toggle State</button>
</div>
`,
styleUrls: ['./component-a.component.css']
})
export class ComponentA {
state: boolean;
constructor(private sharedService: SharedService) {
effect(() => {
this.state = this.sharedService.sharedSignal();
});
}
toggleState() {
this.sharedService.toggleSignal();
}
}
Componente B
import { Component, effect } from '@angular/core';
import { SharedService } from './shared.service';
@Component({
selector: 'app-component-b',
template: `
<div>
<p>Componente B - Estado: {{ state }}</p>
</div>
`,
styleUrls: ['./component-b.component.css']
})
export class ComponentB {
state: boolean;
constructor(private sharedService: SharedService) {
effect(() => {
this.state = this.sharedService.sharedSignal();
});
}
}
Reactividad compartida
En el ejemplo anterior, tanto ComponentA
como ComponentB
están inyectando SharedService
y suscribiéndose a sharedSignal
. Cuando toggleState
se llama en ComponentA
, el valor de sharedSignal
cambia y ambos componentes reaccionan al cambio automáticamente debido a su dependencia reactiva.
Beneficios de compartir Signals con servicios
- Separation of Concerns (SoC): La lógica de estado y reactividad se encapsula en un servicio, separando la manipulación del estado de la representación de la UI.
- Reusabilidad: Un servicio puede ser reutilizado por múltiples componentes, promoviendo la cohesividad y reduciendo duplicaciones de código.
- Mantenibilidad: Actualizar o cambiar la lógica en el servicio afecta automáticamente a todos los componentes que lo consumen, eliminando la necesidad de modificar cada componente individualmente.
- Simplicidad: Simplifica la lógica de los componentes, evitando suscripciones explícitas a observables y gestión manual de suscripciones.
Utilizar servicios para compartir Signals es una práctica recomendada en aplicaciones Angular, permitiendo aprovechar al máximo las capacidades reactivas y declarativas que los Signals proporcionan.
Signals RxJS Interop toSignal() y toObservable()
Angular proporciona integración fluida entre Signals y RxJS a través de métodos dedicados que permiten convertir un Signal en un Observable y viceversa. Esta capacidad interopera entre el modelo de reactividad basado en Signals y el ecosistema RxJS, permitiendo a los desarrolladores combinar lo mejor de ambos mundos según lo requiera la aplicación.
Conversión de un Observable a un Signal (toSignal()
)
El método toSignal()
permite convertir un Observable de RxJS en un Signal en Angular. Esto es útil para integrar flujos de datos existentes de observables, tales como datos de una API, servicios de tiempo real, etc., en el ecosistema de Signals.
Para convertir un Observable a un Signal:
import { toSignal } from '@angular/core/rxjs-interop';
import { of } from 'rxjs';
const myObservable = of(1, 2, 3);
const mySignal = toSignal(myObservable);
// mySignal ahora es un Signal que emitirá los valores 1, 2, y 3 secuencialmente
Los valores que emite el Observable se convertirán en el estado reactivo del Signal, proporcionando compatibilidad directa con el ecosistema de Signals.
Conversión de un Signal a un Observable (toObservable()
)
El método toObservable()
permite convertir un Signal en un Observable de RxJS, facilitando la interoperabilidad cuando se desee usar infraestructura de RxJS, como operadores o combinadores.
Para convertir un Signal a un Observable:
import { signal } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { map } from 'rxjs/operators';
// Crear un Signal
const mySignal = signal(10);
// Convertir el Signal a un Observable
const myObservable = toObservable(mySignal);
// Usar operadores de RxJS en el Observable
const mappedObservable = myObservable.pipe(map(value => value * 2));
mappedObservable.subscribe(value => console.log(value)); // Salida: 20
En este ejemplo, mySignal
se convierte en un Observable, al cual se le aplica el operador map
de RxJS, multiplicando así el valor emitido por 2 antes de suscribirse.
Ejemplo práctico de Signals y RxJS interop
Imaginemos un escenario donde tenemos un servicio que obtiene datos de un servidor a través de un Observable y queremos consumir esos datos en un componente utilizando Signals.
Servicio Angular con Observable
import { Injectable } from '@angular/core';
import { Observable, interval } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DataService {
getData(): Observable<number> {
return interval(1000).pipe(map(val => val * 2)); // Emite un valor cada segundo, multiplicado por 2
}
}
Componente Angular utilizando Signal
import { Component, effect } from '@angular/core';
import { DataService } from './data.service';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-data-display',
template: `
<div>Data: {{ data }}</div>
`
})
export class DataDisplayComponent {
data: number;
constructor(private dataService: DataService) {
// Convertir el Observable a un Signal
const dataSignal = toSignal(this.dataService.getData());
effect(() => {
this.data = dataSignal();
});
}
}
En este ejemplo completo, el DataService
proporciona datos reactivos a través de un Observable que emite nuevos valores cada segundo. El DataDisplayComponent
convierte este Observable a un Signal y lo consume directamente, permitiendo que la UI se actualice automáticamente con cada nuevo valor emitido.
Utilizar toSignal()
y toObservable()
facilita una integración eficiente y elegante entre Signals y RxJS dentro de aplicaciones Angular, aprovechando la reactividad declarativa de Signals sin renunciar a las potentes capacidades de procesamiento de datos de RxJS.
Ejercicios de esta lección Signals en Angular
Evalúa tus conocimientos de esta lección Signals en Angular con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Signals en Angular
Guards funcionales
Decodificar JWT en Angular
Servicio con HttpClient
Ciclo de vida de componentes en Angular
Gestión de productos de Fake Store API
Data binding en Angular
Routes sin módulos en Angular
Router en Angular
Instalación de Angular
Route Guards basados en interfaces
La directiva @if en Angular
Formularios reactivos en Angular
Servicios en Angular
Interceptor funcional
Servicio con Array
La directiva @for en Angular
Interceptores HTTP
Componentes standalone true
Formularios con ngModel en Angular
Routes en Angular
Comunicación entre componentes Angular
Parámetros en rutas con ActivatedRoute
CRUD de Restaurantes y Platos
Tablas en Angular Material
Formulario de registro de usuarios
Instalación y uso de NgBoostrap
Desarrollo de componentes Angular
JWT en Angular
Formularios reactivos en Angular
Formularios en Angular Material
Layout con Angular Material
Effects en Angular
Data binding
HttpClient en servicios de Angular
Desarrollo de módulos Angular
Comandos Angular CLI
Subir archivo en formularios
La directiva routerLink en Angular
Todas las lecciones de Angular
Accede a todas las lecciones de Angular y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Instalación Angular
Introducción Y Entorno
Comandos Angular Cli
Introducción Y Entorno
Desarrollo De Componentes Angular
Componentes
Data Binding En Angular
Componentes
Ciclo De Vida De Componentes En Angular
Componentes
Comunicación Entre Componentes Angular
Componentes
La Directiva @If En Angular
Componentes
La Directiva @For En Angular
Componentes
Componentes Standalone
Componentes
Desarrollo De Módulos Angular
Módulos
Routes En Angular
Enrutado Y Navegación
Router En Angular
Enrutado Y Navegación
La Directiva Routerlink En Angular
Enrutado Y Navegación
Parámetros En Rutas Con Activatedroute
Enrutado Y Navegación
Routes Sin Módulos En Angular
Enrutado Y Navegación
Servicios En Angular
Servicios E Inyección De Dependencias
Httpclient En Servicios De Angular
Servicios E Inyección De Dependencias
Formularios Con Ngmodel En Angular
Formularios
Formularios Reactivos En Angular
Formularios
Subir Archivo En Formularios
Formularios
Layout Con Angular Material
Integración Con Angular Material
Tablas En Angular Material
Integración Con Angular Material
Formularios En Angular Material
Integración Con Angular Material
Instalación Y Uso De Ngboostrap
Integración Con Bootstrap Css
Signals En Angular
Signals Y Reactividad
Effects En Angular
Signals Y Reactividad
Route Guards Basados En Interfaces
Autenticación Y Autorización
Guards Funcionales
Autenticación Y Autorización
Interceptores Http Basados En Interfaz
Autenticación Y Autorización
Interceptores Http Funcionales
Autenticación Y Autorización
Seguridad Jwt En Angular
Autenticación Y Autorización
Decodificar Tokens Jwt En Angular
Autenticación Y Autorización
Certificados de superación de Angular
Supera todos los ejercicios de programación del curso de Angular y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.
En esta lección
Objetivos de aprendizaje de esta lección
- Definir Signals en Angular.
- Consumir Signals en componentes.
- Compartir y gestionar Signals a través de servicios.
- Manejar estados más complejos con Signals.