briebug / ngrx-auto-entity

NgRx Auto-Entity: Simplifying Reactive State
https://briebug.gitbook.io/ngrx-auto-entity/
Other
66 stars 12 forks source link

Custom effects + isLoading$ #235

Closed Inexad closed 1 year ago

Inexad commented 1 year ago

Hi,

I wonder if there is anyway to take control over for example: "isLoading$" property. I'm currently using a custom effect: CustomEffects. The issue is that i want to be able to also set "isLoading$" property here to inform GUI about loading.

Is this possible?

colesanders commented 1 year ago

Hey Inexad, it sounds like you want the isLoading$ observable on the facade to reflect the loading handled by your custom effect. If you're using a custom action to trigger your effect, rather than a built in auto-entity action, then all you should have to do is add that action to your reducer for that state slice.

// Custom Effect
customEffect$ = createEffect(
        () => this.actions$.pipe(
            ofType(customAction),
            map(action => action.criteria),
            switchMap(criteria => 
                this.someService.findForCriteria(criteria).pipe(
                    map(entities => loadAllSuccess({ entities })),
                    catchError(error => of(loadAllFailure({ error })))
                )
            )
        )
    );

 // Custom Reducer

 const reducer = createReducer(initialState,
   on(customAction, state => ({ ...state, tracking: { ...state.tracking, isLoading: true } }))
 );

You should be able to tie any custom actions you want into the reducer to update whatever auto entity state you need to. So the same logic would apply if you had a custom effect that handles entity saving or deleting. If your CustomEffect also dispatches a custom action, then make sure that action is properly reduced and sets isLoading back to false.

Inexad commented 1 year ago

Thanks!

How do i use this reducer combine with others? Currently i have this in a model.state.ts class.

export const { initialState, facade: ModelFacadeBase } = buildState(Model);

export function modelReducer(state = initialState): IEntityState<Model> {
    return state;
}
export const combinedReducers: ActionReducerMap<IFeatureUserState> = {
  model: modelReducer,
  modelTwo: modelTwoReducer,
  modelThree: modelThreeReducer,
  articleGroup: articleGroupReducer
};

How do i add above reducer to my custom action?

jrista commented 1 year ago

@Inexad Can you expound on your need more? Are you trying to have a loading indicator running until ALL of your models there have completed loading? If so, that is definitely possible. You need to do a bit of work with correlation ids and some more specialized effects work to do it. I can help you model the effect you would need if that's what you are looking for. We've actually developed a pattern internally just for this kind of thing in our own usage of auto-entity. There are some details I'd need to spell out for you in terms of exactly how to do it, though. I may try to upload some documentation for such a thing into our gitbook documentation, then link you that, but I'd like to know more about your exact need first.

jrista commented 1 year ago

@Inexad BTW, to clarify what @colesanders shared. The reducer, would in fact, be your existing reducer. So, if you wanted to set isLoading to true on your custom action for say model, then you just update the reducer you already have for that entity, as Cole shared. You can in fact add to any auto-entity reducer to do your own reductions of your own actions. Since auto-entity actually does things entirely normally with NgRx, it doesn't do things in any kind of unusual or special way, you are always free to implement the reducer like you would for any other slice of state, and reduce any action that you need to, for any reason you need to. You have complete and total control if and when you need it. ;)

Inexad commented 1 year ago

Thanks!

@jrista Where do I put the custom reducer code in my existing reducer?

From @colesanders:

const reducer = createReducer(initialState,
   on(customAction, state => ({ ...state, tracking: { ...state.tracking, isLoading: true } }))
);

My reducer:

export const { initialState, facade: ModelFacadeBase } = buildState(Model);

export function modelReducer(state = initialState): IEntityState<Model> {
    return state;
}
jrista commented 1 year ago

@Inexad I would recommend updating your code like so:

export const { initialState, facade: ModelFacadeBase } = buildState(Model);

const reduce = createReducer(initialState,
   on(customAction, state => ({ ...state, tracking: { ...state.tracking, isLoading: true } }))
);

export function modelReducer(state = initialState, action: Action): IEntityState<Model> {
    return reduce(state, action);
}

So you are just using the standard ngrx createReducer function as @colesanders demonstrated, and then updating your existing reducer function to apply the reduce function it creates. That's all there is to it! The nice thing here is, you can actually list any number of custom actions to set isLoading to true. Then the LoadAllSuccess or LoadAllFailure action will automatically set it false again for you. You can also integrate custom state of your own properties into any NgRx Auto-Entity state if you need to, giving you even more control.

Inexad commented 1 year ago

Thanks! This works perfect.

jrista commented 1 year ago

Glad to hear, @Inexad!