0
votes

I have an NgRX state:

import { Item } from './item';

export interface FeatureAState {
    items: Item[];
}

And an action ('[featureA] Add Item') that will add an Item object to an array of Items on my store. I'd like to update this collection via a background effect after each item is added.

export const addItem = createAction(
    '[featureA] Add Item',
    (item: Item = { id: 3, title: 'Duff McKagen' }) => ({ item })
);

I have created an effect to trigger after the reducer has added the item:

updateItems$ = createEffect(() => this.actions$.pipe(
    ofType('[featureA] Add Item'),
    concatMap(() => this.myService.doSomeWork()
        .pipe(
            map(updatedItems => ({ type: '[featureA] Updated', payload: updatedItems }))
        ))
));

From my understanding this requires myService.doSomeWork() to return an Observable? For now I have hard-coded the return to be an Observable created from a new array, so I'm expecting the store items to change to this new array once the effect is finished and the '[featureA] Updated' action is handled by the reducer.

export class MyserviceService {

  doSomeWork() {
    return of([{ id: 6, title: 'Peter Parker' }]);
  }
}

So I now need an action to handle the effect results and update the store? But I don't know how to write the Action or Reducer for this.

export const updatedItems = createAction(
    '[featureA] Updated',
    props<{ items: Item[] }>()
);

// Also tried...

export const updatedItems = createAction(
    '[featureA] Updated',
    (items: Item[]) => ({ items }) // don't understand what I'm doing here, but I guess this action needs to pass through the output of `myService.doSomeWork()` (an Observable mapped to an Action via concatMap()?) for the Reducer to handle?
);
on(updatedItems, (state, { updatedItems }) => ({
    ...state,
    items: updatedItems // I suspect this is not correct, or the 'updatedItems' action is not passing through the correct object
}))

The error I seem to be getting is `ERROR in src/app/featureA/state/featureA.reducer.ts(11,32): error TS2339: Property 'updatedItems' does not exist on type '{ items: Item[]; } & TypedAction<"[featureA] Updated"> & { type: "[featureA] Updated"; }'.`
2
Add your state interface to the questionSGalea

2 Answers

0
votes

Instead of updatedItems, you should use items since you're passing that in your action payload.

on(updatedItems, (state, { items }) => ({
    ...state,
    items
}))
0
votes

It seems the problem was here:

updateItems$ = createEffect(() => this.actions$.pipe(
    ofType('[featureA] Add Item'),
    concatMap(() => this.myService.doSomeWork()
        .pipe(
            map(updatedItems => ({ type: '[featureA] Updated', payload: updatedItems })) // <= HERE
        ))
));

I didn't understand payload is just a property for my action and doesn't have to be called payload. As it was I was passing 'payload' to the action, but not referencing it in the action or reducer. In fact I didn't event need to reference it in the Action either. So this will update the store based on my service response:

export const updatedItems = createAction(
    '[featureA] Updated'
);
on(updatedItems, (state, { payload }) => ({
    ...state,
    items: payload
}))

As will this:

updateItems$ = createEffect(() => this.actions$.pipe(
    ofType('[featureA] Add Item'),
    concatMap(() => this.myService.doSomeWork()
        .pipe(
            map(updatedItems => ({ type: '[featureA] Updated', items: updatedItems }))
        ))
));
on(updatedItems, (state, { items }) => ({
    ...state,
    items
}))

I get a Typescript error though, which can be fixed by updating the Action with either of these:

export const updatedItems = createAction(
    '[featureA] Updated',
    props<{ items: Item[] }>()
);

// or

export const updatedItems = createAction(
    '[featureA] Updated',
    (items: Item[]) => ({ items })
);

In the end I've gone with this, which seems more explicit about what's going on:

updateItems$ = createEffect(() => this.actions$.pipe(
    ofType('[featureA] Add Item'),
    concatMap(() => this.myService.doSomeWork()
        .pipe(
            map(updatedItems => ({ type: '[featureA] Updated', payload: updatedItems }))
        ))
));
export const updatedItems = createAction(
    '[featureA] Updated',
    (payload: Item[]) => ({ payload })
);
on(updatedItems, (state, { payload }) => ({
    ...state,
    items: payload
}))