5Hola a todos, hoy os voy a dar una guía completa sobre la nueva funcionalidad de Angular, signals.
Angular signals es la mejora mas esperada por los desarrolladores de Angular, esto se debe a que hace que nuestras aplicaciones sean más reactiva.
Por ejemplo, si tenemos una variable que depende de otras dos, si uno de ellas cambia, automáticamente, cambiará el valor de esa variable.
Características signals
- Programación reactiva: Signals sigue el paradigma de programación reactiva, que se centra en la propagación de cambios y eventos a través de un flujo de datos. Esto permite manejar fácilmente eventos asíncronos y reactivos, como respuestas de API, eventos de usuario y actualizaciones en tiempo real.
- Composición de operaciones: Signals permite componer operaciones complejas de manera sencilla y legible. Puedes encadenar múltiples operaciones para transformar, filtrar, combinar y manipular flujos de datos. Esto facilita la implementación de lógica compleja y reduce la cantidad de código necesario.
- Manejo elegante de errores: Signals proporciona operadores para manejar de manera efectiva los errores en flujos de datos. Puedes manejar errores de manera centralizada en un flujo de datos y tomar decisiones basadas en el tipo de error. Esto mejora la robustez y el manejo de errores en tu aplicación.
- Cancelación y liberación de recursos: Los flujos de datos en Signals son cancelables, lo que significa que puedes detener la emisión y el procesamiento de datos en cualquier momento. Esto es especialmente útil cuando trabajas con flujos de datos largos o costosos en recursos. Además, Signals se encarga automáticamente de liberar recursos cuando ya no se necesitan, lo que previene posibles fugas de memoria.
- Integración con Angular: Signals está estrechamente integrado con el ecosistema de Angular y se utiliza ampliamente en muchos componentes y servicios de Angular, como el enrutamiento, las solicitudes HTTP y los formularios reactivos. Al utilizar Signals, puedes aprovechar al máximo las capacidades de Angular y crear aplicaciones más eficientes y mantenibles.
Creación, modificación y acceso de signals
Para crear un signal, usamos signal y el valor que queremos darle, por ejemplo, un signal con un valor de 5.
const a = signal(5);
Otra forma, es usando el método set de signal. También sirve para modificar el valor como tal.
const a = signal(0); a.set(5)
Si se quiere actualizar el signal, usaremos el método update.
const a = signal(5); a.update(value => value + 1); // Ahora vale 6
La forma de acceder a un valor de un signal es llamarlo como una función.
const a = signal(5); console.log("Valor a: " + a()); // Devuelve 5
WritableSignal y Signal
Mientras estes mirando la parte de signals, veras que hay dos clases llamadas WritableSignal y Signal, pero ¿en que se diferencian?
WritableSignal permite modificar el valor y Signal no. Por lo que Signal no puede usar el método set.
Método signals: computed
En ocasiones, tendremos valores que dependan de otros valores, cuando uno de ellos cambie debería afectar al resto, esto lo podemos hacer con el método computed. Veamos un ejemplo:
const a = signal(5); const b = signal(10); const c = computed(() => a() + b()); console.log(c()); // su valor es de 15 a.set(10); // Modificamos el valor del signal a console.log(c()); // su valor ahora es de 20
Método signals: effects
En ciertas ocasiones, puede ser que necesitamos monitorizar los cambios que se vayan haciendo los signals.
Esto lo podemos hacer con el método effects, cuando cualquier signal cambie ejecutará esa función. Veamos un ejemplo:
const a = signal(5); const b = signal(10); effect(() => { console.log("Valor de a: " + a()); console.log("Valor de b: " + b()); }); a.set(15); b.set(20);
¡Importante! Este effect solo se puede usar en el constructor, si lo quieres usar fuera de este, tienes que usar Injector. Veamos un ejemplo:
@Component({...}) export class AppComponent implements OnInit { constructor(private injector: Injector) { } ngOnInit(): void { const a = signal(5); const b = signal(10); effect(()) => { console.log("Valor de a: " + a()); console.log("Valor de b: " + b()); }, { injector: this.injector }); a.set(15); b.set(20); effect(() => { console.log(`${JSON.stringify(this.products())}`); console.log(`${this.total()}`); }, { injector: this.injector }); } }
Signals con objetos
Es muy probable que tarde o temprano tengamos que usar arrays u objetos con signals, tambien podemos. Veamos un ejemplo:
const a = signal([ { name: "product 1", price: 100 } ]) console.log(JSON.stringify(a()))
Método signals: mutate
Si necesitamos actualizar propiedades sin necesidad de actualizar el valor completo, podemos usar el método mutate. Ideal para arrays de objetos, veamos un ejemplo:
const a = signal([ { name: "product 1", price: 100 }, { name: "product 2", price: 200 }, { name: "product 3", price: 300 } ]); a.mutate(value => value.forEach(v => v.price = v.price * 2)) console.log(JSON.stringify(a()))
Ejemplo práctico de signals
Veamos un ejemplo práctico de todo lo visto en este tutorial.
Nos creamos un proyecto de Angular, sino sabes como hacerlo, te dejo un tutorial aquí:
Creamos un componente llamado store.
Dentro de este, metemos una carpeta models con el siguiente fichero:
export class Product { name: string; price: number; quantity: number; }
TS
import { Component, Injector, OnInit, Signal, computed, effect, signal } from '@angular/core'; import { Product } from './models/product'; @Component({ selector: 'app-store', templateUrl: './store.component.html', styleUrls: ['./store.component.css'] }) export class StoreComponent implements OnInit { // Signal para llevar el precio total de los productos public total: Signal; // Signal para llevar el total de los productos public numProducts = signal(0); // Signal para llevar los productos public products = signal<Product[]>([]); // Necesito Injector por effect constructor(private injector: Injector) { } ngOnInit(): void { // Inicializo los productos this.products.set([ { name: 'Product 1', price: 100, quantity: 1 } ]); // Inicializo el numero de productos this.numProducts.set(this.products().length); // Effect para mostrar los cambios effect(() => { console.log(`${JSON.stringify(this.products())}`); console.log(`Num products: ${this.numProducts()}`); console.log(`Total: ${this.total()}`); }, { injector: this.injector }); // Si cambia los productos, modifico el total this.total = computed(() => { let sum = 0; for (const p of this.products()) { sum += p.price * p.quantity; } return sum; }); } less(p: Product) { if (p.quantity > 0) { // Actualizo el producto this.products.mutate(() => p.quantity--); } } more(p: Product) { // Actualizo el producto this.products.mutate(() => p.quantity++); } addProduct() { // Añado un producto this.products.mutate(products => products.push({ name: 'Product ' + this.numProducts(), price: Math.floor(Math.random() * 1000) + 100, quantity: 1 }) ); // Actualizo el numero de productos this.numProducts.update(value => value + 1); } reset() { // Todos los productos tienen un elemento de cantidad this.products.mutate(products => products.forEach(p => p.quantity = 1)); } }
HTML
</pre> <div><input name="" readonly="readonly" type="text" value="" /> <input readonly="readonly" type="number" value="" /> <input readonly="readonly" type="number" value="" /> <button>-</button> <button>+</button></div> <pre>Total: {{ total() }} Num products: {{ numProducts() }} <button>Add product</button> <button>Reset</button>
Aquí lo tenéis ya completo:
No olvides visitar el blog oficial de Angular que habla sobre este tema.
Os lo dejo en un repositorio de github.
https://github.com/DiscoDurodeRoer/example-angular-signals
Os dejo un video donde lo realizamos paso a paso.
Espero que os sea de ayuda. Si tenéis dudas, preguntad. Estamos para ayudarte.
Deja una respuesta