-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update / Create objects callback #158
Comments
Hi @Inexad! Thank you for the report! Indeed, this is a bit of a rough area in NgRx Auto-Entity right now. It is also an area we are hoping to address in the near future. We do not yet have an ideal solution that would allow end developers to solve this problem easily, but we are working on it. Now, we have made some small strides toward supporting this kind of functionality. You should be using custom effects, rather than the selections on the facades, to do the job. That said, it is a non-trivial problem to solve. I will have to get back to you with a more detailed explanation, however for now:
With the above, and with some custom actions to assist in coordinating the rest of the process, it IS possible to perform other actions IN RESPONSE TO the success (or failure) of a create, update or delete of another entity. We have some other plans for the future to support these scenarios as well. We may introduce another "stage" in the standard initiation/result (i.e. create / createsuccess|createfailure) flow. A third, intermediate stage, between the initiation (create), to handle the preliminary result (intermediate/preliminary result handler), and the final result (existing result handler). This three-stage approach has been discussed and is planned...there is an issue we created to track the idea: We also have had discussions about the addition of some kind of We have also been considering the concept of "batched" processes. This would basically allow some kind of token to be created that can track the progress of multiple actions, and support easier coordination of multi-stage processes. We have an issue created to track that idea as well: In the mean time, using the 0.5.0 beta features to support the creation of "Correlated" and "Coordinated" workflows with NgRx Effects is the best way to solve the problem you have right now. I'll provide more detail with example code as soon as I get a chance. |
Also within the 0.5.0 betas, the actionable facade methods return the correlationId (you can also create your own and pass it to the method if you'd like). const correlationId = this.ordersFacade.update(objectToUpdate); |
@jrista @schuchard I will use correlationId for now and i'm very excited for upcoming updates of this great package! I'm currently migrating some of our business system using this package, and it works good so far ! Regards. |
Let us know if you run into any other issues. I'll also probably reply to this issue with some additional code examples later this evening, as I want to make sure you understand all the little nuances of "correlating" actions. |
@Inexad Correlation is a more complex topic, and since it is taking some time to get the details together, I've decided to just add it all to our git book docs. Keep an eye out there for the info on how to coordinate. |
@jrista Thanks! I will keep checking the git book docs. Regarding callback functionality, wouldn't it be possible to pass in a callback function for each function that is invoked after completion ? like:
Regards |
So the facades are simply there to facilitate using NgRx. That means selecting data and dispatching actions. Dispatching an action initiates an asynchronous process...and this is explicitly by design. Calling load on a facade does not mean that we make an http call directly, and can then just "wait" for its response. Calling load on a facade simply dispatches an ngrx action that ultimately results in data being loaded. The fundamental design of NgRx is to EXPLICITLY decouple things. One of the things decoupled when using NgRx is requesting that data be loaded, and getting the data once it is available. It is an express design decision and therefor also an express choice to use this decoupled process when using NgRx...in general. One of our goals with NgRx Auto-Entity is to use NgRx as it was designed as well, and not try to change those aspects of the framework. So, when you use an NgRx Auto-Entity facade...the express and intended design is that if you want to load data, which is intended to get that data into state (not necessarily anywhere else), then you are then intending to also use the appropriate selection (i.e. all$, or sorted$, etc.) to retrieve the data from state (note...from state, not from a server or anywhere else) for display in your UI, or use in effects, etc. We strongly believe in the benefits that NgRx brings to the table by decoupling data retrieval and state...from data selection and use. Further, when it comes to synchronizing behavior. This should also be done primarily through effects. There is a recommended design here when using NgRx Auto-Entity that I'll be outlining in some of the upcoming documentation. Generally speaking, with an Auto-Entity app, you would have several "layers" of code, and generally speaking, layers communicate "down" to the layers below, but not "up" to layers above:
The vast majority of your code should ultimately reside in effects and selectors when using NgRx in general. A facade layer is largely just there to bridge the gap between UI and the rest of the application, and facilitate reuse. So when it comes to coordinating behavior, that would largely be done in the effects. So in your original post, you chose to update some object, then you needed to do something once it was updated. That would all be effects: export const saveOrderAndToast = createAction('[Orders] Save and Toast', props<{ order: Order, correlationId: string }>());
export class OrderFacade extends OrderFacadeBase {
// ...standard facade setup...
save(order: Order): void {
this.store.dispatch(saveOrderAndToast({order, correlationId: /* generate random correlation id */});
}
}
export class OrderEffects {
constructor(private actions$: Actions, private toasts: ToastService) {}
updateOrder$ = createEffect(
() => this.actions$.pipe(
ofType(saveOrderAndToast),
map(({order, correlationId}) => new Update(Order, order, correlationId))
)
);
toastOnOrderSaveSuccess$ = createEffect(
() => combineLatest([
this.actions$.pipe(ofType(saveOrderAndToast)),
this.actions$.pipe(
ofEntityType(Order, EntityActionTypes.UpdateSucceeded),
timeout(30000), // If you don't want to wait indefinitely, set up a timeout...
catchError(() => null) // ... then convert any error to null (timeout throws if it times out!)
),
]).pipe(
// When we get an update success action dispatched where the correlationId matches our initiating
// action 'saveOrderAndToast', we continue:
filter(([{correlationId}, success]) => !!success && success.correlationId === correlationId),
switchMap(([, {entity}]) =>
// If you use services that return promises, you can use `switchMap` or `from` to turn them into observables!
from(this.toasts.showToast('Order Saved', `Successfully updated order ${getKeyFromModel(Order, order)}`))
)
)
);
} So the high level, simple approach here is:
In my above example, I have only handled success. You could also handle failure, or only failure, with another effect. You could handle success and failure in a single action. You can do a lot of things once you start using effects, as that is, generally, how NgRx works. Effects observe action streams, filter actions, join actions and data from other streams (i.e. selectors), and dispatch other actions. This, ultimately, creates "workflows" that encapsulate the vast majority of your business logic. Auto-Entity is just here to take care of most of the entity state creation boilerplate. Instead of having to write dozens to hundreds of lines of code per entity, you can write about 10 or so. Once you have created an auto entity, however, you should still be using NgRx largely how you would have used it before. |
@jrista Thanks so much for this throughout explanation! Regards! |
Hi,
It would be great to be able to add a callback functionality when deleting / updating objects.
Now you can use isDeleting$/isUpdating$, the problem is that it's hard to get the affected object.
Sometimes when removing / updating objects you need to perform actions after. Like removing it from another objects collection, call other function and so on.. The actions is needed to be performed after the actually server-side call.
Currently i'm doing it like this (which is not very clean):
Maybe i'm looking at this from wrong perspective ?
Regards.
The text was updated successfully, but these errors were encountered: