La introducción de las señales ha provocado una gran tormenta en la comunidad de desarrolladores de Angular (y también fuera de ella). Y con razón. Veamos por qué hay tanto revuelo a su alrededor.
Ventajas de los Signals
Rendimiento
Con los signals, las aplicaciones de angular pueden tener una reactividad de grano fino. Lo que significa que sólo los nodos DOM que necesitan ser actualizados serán actualizados. Esto potencialmente puede hacer que la aplicación angular sea muy eficiente acerca de sus actualizaciones DOM y la detección de cambios.
En contraste, actualmente en Angular todo el árbol pasa por una comprobación sucia porque el framework no sabe qué parte del DOM depende de qué parte del modelo de vista.
Además, con los signals por fin podemos tener una aplicación sin zonas (zone-less application`
), ya que el framework recibirá notificaciones de cualquier cambio que se produzca en el modelo de vista que utiliza signal.
Incluso en la versión actual de Angular sin signal todavía podemos conseguir una aplicación de alto rendimiento, pero los signal harán que sea difícil disparar a nosotros mismos en el pie y accidentalmente crear problemas relacionados con el rendimiento.
El desarrollador no tiene que preocuparse de qué changeDetectionStrategy utilizar, o llamar a funciones es plantilla, o memoize funciones o el uso de tuberías. Todas estas mejores prácticas para construir una aplicación performant pueden llegar a ser irrelevantes. Menos cosas que aprender siempre es bueno
Signal y RXJS
Cualquier aplicación angular de moderada a compleja tiene muchos usos de rxjs. Aunque rxjs es una librería muy útil, puede no ser fácil de entender para muchos desarrolladores. Para crear aplicaciones reactivas, y manejar condiciones de carrera rxjs se convierte en esencial. No se puede vivir con ella, no se puede salir sin ella.
Pero con las signals que potencialmente puede deshacerse de la mayor parte del código rxjs. Esto hará angular mucho más acogedor para los nuevos desarrolladores y aumentar la legibilidad general de la base de código.
Distinción entre rxjs y signals
Al repasar los ejemplos de uso de signals, muchos desarrolladores pueden pensar que se trata simplemente de rxjs con diferentes apis. La similitud es asombrosa, especialmente con BehaviourSubject
//Example behaviourSubject usage
const subject = new BehaviourSubject(0);
subject.getValue();
subject.next(1);
subject.subscribe((data) => {
console.log(data)
})
//Example signal usage
const sig = signal(0);
sig()
sig.set(1)
effect(() => {
console.log(sig())
})
También signal no requiere distinctUntilChange
o shareReplay
para la multidifusión, a diferencia de Observable en la mayoría de los casos.
¿Entonces no necesitamos rxjs?
Bueno, no exactamente. Porque signal es siempre sync. Son muy buenos candidatos para almacenar estados o el modelo de vista. Pero las signal no son muy buenas candidatas para cosas asíncronas, como eventos o llamadas XHR. Para esos todavía necesitaríamos rxjs, sobre todo.
El ejemplo clásico es el caso de uso search-as-you-type
, donde aprovechamos, debounceTime distinctUntilChanges
y switchMap
const searchValue$: Observable<string> //lets assume this is comes from input
const result$ = searchValue$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((input) => {
return this.http.get('/seach?' + input)
})
)
Para convertir esto en signal de uso, podríamos saltar de signal a observable y luego de vuelta a las signal
const searchValue: Signal<string>
const searchValue$ = fromSignal(searchValue); //convert signal to observable
const result$ = searchValue$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((input) => {
return this.http.get('/seach?' + input)
})
)
const result = fromObservable(result$) //convert observable back to signal
En un mundo en el que las señales se adopten por completo, podríamos ver rxjs utilizado con moderación sólo para tales casos de uso. Es un poco incómodo seguir saltando entre la signal y la forma rxjs de escribir código. ¿Pero tal vez podamos eliminar rxjs por completo incluso para estos casos?
const searchValue: Signal<string>
const debouncedSearchValue = signal(searchValue());
const results: WritableSignal<Result[]> = signal([])
/**
* This effect add debounced search term in a new signal
*/
effect(() => {
const search = searchValue();
const timeout = setTimeout(() => {
debouncedSearchValue.set(search)
});
return () => {
clearTimeout(timeout)
}
})
/**
* This effect uses debounceSearchValue instead of searchValue
* to trigger api call
*/
effect(() => {
const subscription = this.http.get('/seach?' + debouncedSearchValue())
.subscribe((res) => {
results.set(res)
})
return () => {
subscription.unsubscribe()
}
})
Si estás comparando el código con y sin rxjs, por supuesto, el que utiliza rxjs parece mucho más conciso. Vamos a refactorizarlo un poco para hacerlo más conciso. 🤩
const searchValue: Signal<string>
const debounceSearchValue = debouncedSignal(searchValue);
const results: WritableSignal<Result[]> = signal([]);
effect(() => {
const subscription = this.http.get('/seach?' + debouncedSearchValue())
.subscribe((res) => {
results.set(res)
})
return () => {
subscription.unsubscribe()
}
})
Esto se ve mejor. Todavía menos conciso que el de rxjs, pero no necesitamos aprender rxjs para entender esto. Esto es mucho más amigable para principiantes.
Quizás te estás preguntando si debouncedSignal
función utilizada anteriormente se parece a esto:
function debouncedSignal<T>(input: Signal<T>): Signal<T> {
const debounceSignal = signal(input());
effect(() => {
const value = input();
const timeout = setTimeout(() => {
debounceSignal.set(value)
});
return () => {
clearTimeout(timeout)
}
});
return debounceSignal
}
Conclusión
Sí, las signals pueden sustituir a los rxjs. Pero quizás no completamente. rxjs aporta concisión y signals facilita la lectura.
¿Cómo evolucionarán estos casos de uso y patrones?
Eso es algo que todos veremos a su debido tiempo y los desarrolladores empezarán a explorar más. Pero las signals están aquí para cambiar la forma en que el código en Angular y el bombo es real.