0

I am working on an angular2 application which uses ngrx store approach to manage states. App is open source on github here

Problem statement

Specific problem I am facing with this approach is using values emitted from one observable when other observable returns null.

I don't want to query backend api when I have data present in my ngrx store.


Angular2 Code

Below is my trips.reducer.ts file

export interface State {
  ids: string[];
  trips: { [id: string]: Trip };
  selectedTripId: string;
}

const initialState = {
  ids: [],
  trips: {},
  selectedTripId: null
}

export function reducer(state = initialState, action: Action ): State {}

export function getTrips(state : State) {
  return state.trips;
} 

export function getTripIds(state: State) {
  return state.ids;
}

export function getSelectedTripId(state: State) {
  return state.selectedTripId;
}

Below is my base reducer index.ts

export interface State {
  trips: fromTripsReducer.State;    
} 

const reducers = {
  trips: fromTripsReducer.reducer,
}

export function getTripsState(state: State): fromTripsReducer.State {
  return state.trips;
}

export const getTrips = createSelector(getTripsState, fromTripsReducer.getTrips);
export const getTripIds = createSelector(getTripsState, fromTripsReducer.getTripIds);
export const getSelectedTripId = createSelector(getTripsState, fromTripsReducer.getSelectedTripId);
export const getSelectedCityId = createSelector(getTripsState, fromTripsReducer.getSelectedCityId);

export const getTripsCollection = createSelector(getTrips, getTripIds, (trips, ids) => {
  return ids.map(id => trips[id]);
});

export const getSelectedTrip = createSelector(getTrips, getSelectedTripId, (trips, id) => {
  return trips[id];
});

Now I can get a particular trip in trip-detail.component.ts like this

selectedTrip$: Trip;

constructor(private store: Store<fromRoot.State>) {
  this.selectedTrip$ = this.store.select(fromRoot.getSelectedTrip);
}

Now if I reload the route localhost:4200/trips/2, then our store will initialize to initialState that is shown below

const initialState = {
  ids: [],
  trips: {},
  selectedTripId: null
}

and below method will not work as getTrips and getSelectedTripId will be null

export const getSelectedTrip = createSelector(getTrips, getSelectedTripId, (trips, id) => {
  return trips[id];
});

So now I can make a backend request which will load only the single trip based on the url id like this

return this.http.get(`${this.apiLink}/trips/${trip_id}.json`
  .map((data) => data.json())

But i want to make backend request only when the trip is not present in the store and

this.selectedTrip$ returns null or undefined.

this.selectedTrip$ = this.store.select(fromRoot.getSelectedTrip);
chandradot99
  • 3,616
  • 6
  • 27
  • 45

1 Answers1

0

If you need the data to be ready before the component is being shown you could use a resolver. See this answer here.

In your case it would look the following and the resolver would only make sure that the loading of the data is initialized if selectedTrip is null. Note: As we won't use the returned data of the resolver anywhere, we can just return anything.

@Injectable()
export class SelectedTripResolver implements Resolve {

constructor(
    private store: Store
) {}

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {

    // get the selectedTrip
    return this.store.select(fromRoot.getSelectedTrip)
        // check if data is ready. If not trigger loading actions
        .map( (selectedTrip) => {
            if (selectedTrip === null) {
                //trigger action for loading trips & selectedTrip
                this.store.dispatch(new LoadTripAction());
                this.store.dispatch(new LoadselectedTripAction());
                return false; // just return anything
            } else {
                return true; // just return anything
            }
        });

}

Here the resolver will make sure that the load actions are triggered when the selectedTrip data is not ready.

In the trip-detail.component you'll only need to wait for valid data. Like so:

constructor(private store: Store<fromRoot.State>) {
    this.selectedTrip$ = this.store.select(fromRoot.getSelectedTrip)
        .filter(selectedTrip => selectedTrip !== null);
}

Hope that makes sense and helps you.

Community
  • 1
  • 1
chrigu
  • 816
  • 1
  • 7
  • 22