J King
J King

Reputation: 4434

check store for object before calling api

You know how they say you don't need state management until you know you need it. Well turns out my project needs it. So I need some help wit best practice as I am adding ngxs to an existing angular project.

I have an action called getServiceDetail and my statemodel has a list of objects called DriverListsStopInfoViewModel. each of these objects have a unique ID. The html template of the consuming component uses a selector for the property currentStopDetail, which is a state property that gets set in my action.

GOAL:

in my action I want to check the list of objects in my store to see if an object with the same id exists and return that object, and if it does not exist, call and api to get it.

EXAMPLE:

The following code works, but I would like to hear if this is the right way to do it. do I even need to return the object from the action function if its found, or can I just use patch state to assign it to the currentStopDetail

export interface SignServiceStateModel {
  searchResults: ServiceSearchModel[];
  driverStopsDetails: DriverListsStopInfoViewModel[];
  driverStopsList: DriverListsStopsViewModel[];
  driverStopsMarkers: DriverStopsMarkerViewModel[];
  currentStopDetail: DriverListsStopInfoViewModel;
}

const SIGNSERVICE_STATE_TOKEN = new StateToken<SignServiceStateModel>(
  'signservice'
);

@State<SignServiceStateModel>({
  name: SIGNSERVICE_STATE_TOKEN,
  defaults: {
    searchResults: [],
    driverStopsDetails: [],
    driverStopsList: [],
    driverStopsMarkers: [],
    currentStopDetail: null
  },
})
@Injectable()
export class SignServiceState {
  constructor(private driverListsService: DriverListsService) {}

  
  @Action(DriverList.GetServiceDetail)
  getServiceDetail(
    ctx: StateContext<SignServiceStateModel>,
    action: DriverList.GetServiceDetail
  ) {
    if (action.serviceId === undefined || action.serviceId <= 0) {
      return;
    }
    // check if record already in list and return
    const currentState = ctx.getState();
    const existingStopDetail  = currentState.driverStopsDetails.find(s => s.SignServiceId === action.serviceId);
    if (existingStopDetail  !== undefined) {
      const currentStopDetail = existingStopDetail;
      ctx.patchState({ currentStopDetail });
      return currentStopDetail;
    }
    // else get new record, add it to list and return
    return this.driverListsService.getDriverListsInfo(action.serviceId).pipe(
      tap((currentStopDetail) => {
        ctx.patchState({ currentStopDetail });
        ctx.setState(
          patch({
            driverStopsDetails: append([currentStopDetail])
          })
        );
      })
    );
  }


  @Selector()
  static currentStopDetail(state: SignServiceStateModel) {
    return state.currentStopDetail;
  }
}

I only included the relevant code from my state class

QUESTION:

is this the best way to check the store for an item and call api if it does not exist?

Thanks in advance

Upvotes: 1

Views: 969

Answers (1)

Garth Mason
Garth Mason

Reputation: 8011

Short answer is yes, what you have done here is a typical way of handling this scenario (in my experience). There's a couple of improvements you could make:

do I even need to return the object from the action function if its found, or can I just use patch state to assign it to the currentStopDetail

No, you don't return anything from these action handlers, other than possibly an Observable that NGXS will handle (so in your case if there is no matching item found, you return the Observable that fetchs it from the API and patches the state).

Also when you do make the API call, you should only need a single update to the state:

return this.driverListsService.getDriverListsInfo(action.serviceId).pipe(
  tap((result) => {
    ctx.setState(
      patch({
        currentStopDetails: result
        driverStopsDetails: append([result]),
      })
    );
  })
);

Upvotes: 2

Related Questions