I love the maintainability given by the abstractions provided by a good store pattern, but I also feel the loss of knowing exactly when certain asynchronous functions are completed. Also, with the older, full Ngrx Redux-compatible store, I could publish success and failed actions that contained information about exactly which things just completed or failed, etc. I'm struggling to handle many features with the new SignalStore that were effortless before.
Using plain signals, I can know exactly when a particular operation has completed and have my UX respond accordingly. In this particular scenario, I have a list of items on the screen, each with its own "delete" button, and I want to both disable and change the text of a clicked button to "deleting...". I need to keep track of exactly which item is being deleted, and when the delete is complete. I can get this to work via the following in my component...
productUx = new Map<IProduct, { deleting: boolean }>();
onDelete(product: IProduct) {
this.productUx.set(product, { deleting: true });
this.productService.delete(product.id)
.pipe(
takeUntil(this.ngUnsubscribe),
finalize(() => {
this.productUx.set(product, { deleting: false });
})
)
.subscribe();
}
I have a productUx Map object that basically just extends an IProduct, allowing me to add Ux specific properties to any entity. I also have an async-button directive that just needs a boolean variable to trigger the change in button state. This works because I know when async operations complete. Moving to an Ngrx SignalStore, I need to handle all of these async flags inside the store proper. I was able to get it to work with the following in my store...
export interface IProductState {
deleting: Map<EntityId, boolean>;
}
const _delete = async (id: EntityId) => {
store.deleting().set(id, true);
await entityService.deleteAsPromise(id);
patchState(store, removeEntity(id)); // I'm using withEntities<IProduct>()
store.deleting().set(id, false);
}
...then in my component html...
<button (click)="onDelete(product)" app-async-button [asyncInProgress]="productStore.deleting().get(product.id)">Delete</button>
This appears to be working, but it feels "off" to me. I need to know when something both begins and ends on a per-entity basis. Is there a better or more elegant way to handle what are basically on/off flags on a per-entity basis inside an Ngrx SignalStore?
Signal store is extensible, this means you can write custom (generic) features and plug them into a signal store. A "request" state is a good case for this.
As an example see the example: