combine two selectors or call Effect inside another effect?

76 Views Asked by At

I have a List of items with properties. Here is a model for it:

export interface Banner {
    id: number;
    name: string;
    channelId: string;
    language: string;
    zoneId: string;
    priority: number;
    fileId: string;
    startDate: Date;
    endDate: Date;
    active: boolean;
    labels: string[];
}

My service returns All of above properties. But I have to call second service which returns image url by the fileid From the first service response.

I am using ngrx state managment. Here are my actions:

export const getImageById = createAction(
    BannerActionTypes.GetImageById,
    props<{ id: string }>()
);
export const getImageByIdSuccess = createAction(
    BannerActionTypes.GetImageByIdSuccess,
    props<{ url: string }>()
);
export const getImageByIdFail = createAction(
    BannerActionTypes.GetImageByIdFail,
    props<{ error: Error | any }>()
);

export const loadBanners = createAction(
    BannerActionTypes.LoadBanners,
    props<{ filter: Filter }>()
);
export const loadBannersSuccess = createAction(
    BannerActionTypes.LoadBannersSuccess,
    props<{ response: BannerResponse }>()
);
export const loadBannersFail = createAction(
    BannerActionTypes.LoadBannersFail,
    props<{ error: Error | any }>()
);

And my effect is:

export class BannerEffect {
    constructor(private actions$: Actions, private bannersService: BannersService) { }

    loadBanners$ = createEffect(() =>
        this.actions$.pipe(
            ofType(BannerActions.loadBanners),
            switchMap((s) =>
                this.bannersService.getAll(s.filter).pipe(
                    map((response) => BannerActions.loadBannersSuccess({ response })),
                    catchError((error) => of(BannerActions.loadBannersFail({ error })))
                )
            )
        )
    );
    getImage$ = createEffect(() =>
        this.actions$.pipe(
            ofType(BannerActions.getImageById),
            switchMap((s) =>
                this.bannersService.getImageById(s.id).pipe(
                    map((url) => BannerActions.getImageByIdSuccess({ url })),
                    catchError((error) => of(BannerActions.getImageByIdFail({ error })))
                )
            )
        )
    );
}

So my question is, where Should I combine these two service calls? Should I Call getImageById in loadBanners effect after it loads successfully Or Should I combine this two in selector? What's the best approach. I want to have all properties with image URL in one place.

1

There are 1 best solutions below

0
Volodymyr Usarskyy On

You haven't provided your state, so currently I am just guessing what shape it has.

Lets assume that each list item has a type Banner (i.e., it wasn't extended) and imageUrl is always available and really must be in the global state. In this case you have a problem with your state because required Banner's property is stored somewhere else. This will definitely lead you to a situation when you have incomplete data in your state and you will be forced to implement some hacky retries of the second service call. IMHO, this is poor state design and should be avoided at all costs. Selector doesn't solve the problem, it just makes it worse by adding additional code.

I would build a state using an extended version of Banner type that includes imageUrl property. Then I would chain service calls inside one effect, so at the end of the execution you get "all or nothing".