Reaccionando a las interacciones del usuario con Signals
Ahora que tenemos Signals con Angular 16, la pregunta es cómo manejamos flujos de datos asíncronos con interacciones del usuario.
Es posible que sepas de la idea de un Action Streams , que Deborah Kurata, si no me equivoco, inventó, y estos representan las interacciones del usuario.
Para recapitular, la idea es crear un Observable
a partir de un BehaviorSubject
previamente definido que luego se actualizaría basado con las interacciones del usuario.
selectedCompany = new BehaviorSubjectstring>('');
selectedCompanyAction$ = this.selectedCompany.asObservable();
selectCompany(value: Event)
const newValue = (value.target as HTMLSelectElement).value;
this.selectedCompany.next(newValue);
}
Gracias a crear un nuevo Observable
a partir de la interacción del usuario, ahora puedes reaccionar a cambios hechos por el usuario, cargar más datos o mezclarlo con otros flujos asíncronos (Observable
).
public filteredUsers$ = combineLatest([
this.manageUsersFacade.users$,
this.selectedCompanyAction$,
]).pipe(
map([users, company]) =>
company === ''? users: users.filter((user) => user.company === company)
)
);
El patrón de crear “Action Stream” fue muy efectivo y ayudó a mejorar la detección de cambios, ya que todo podía ser tratado como un Observable
y podías confiar en la | async
para manejar los datos y evitar estar usar subscribe
en cada uno de los observables que tienes.
Y entonces, ¿cómo se logra esto con Signals?
signal
es un envoltorio alrededor de un valor que puede notificar a los consumidores interesados cuando ese valor cambia. Las señales pueden contener cualquier valor, desde primitivas simples hasta estructuras de datos complejas.
La primera parte consiste en transformar el “Action Stream” a un Signal. Para hacer esto, puedes convertir el observable
usando la función toSignal
o selectSignal
si estás utilizando NgRx.
const usersSignal = toSignal(this.usersApiService.list());
users = this.store.selectSignal(selectAllUsers)
Luego, para crear la “Action Signal”, debemos declararla con un valor inicial y modificarla una vez que el usuario realice un cambio:
selectedCompany = signal<string>('');
selectCompany(value: Event){
const newValue = (value.target as HTMLSelectElement).value;
this.selectedCompany.set(newValue);
}
El paso final es reemplazar el combineLatest
que usábamos cuando trabajábamos con observables. Para hacer esto, vamos a utilizar el método computed que lee valores de ambos flujos y crea una nueva señal cada vez que alguno de ellos cambia. Así es cómo queda:
//👇 Crea la `Signal` que tiene los usuarios filtrados
public filteredUsers = computed(() => {
// 👇 Signal con los usuarios que vienen del API
const users = this.manageUsersFacade.users();
// 👇 "Action Signal" para las interacciones del usuario
const company = this.selectedCompany();
return company === ''
? users
: users.filter((user) => user.company === company);
});
Conclusión
Puedes declarar “Action Signals” que se actualiza cada vez que hay una interacción del usuario y utilizar compute
para crear nuevas señales a partir de otras, al mismo tiempo que disfrutas de los beneficios de tener un código más eficiente sin observables y suscripciones.
Aquí tienes un ejemplo de código: