Search code examples
angular6ngrxngrx-storengrx-effects

No data of first load


I'm new to NgRx, and trying to retrieve and cache paginated table data using Effects and http request. But on any first time page load (if page isn't cached already) I got empty page, even though if I do console.log of state object, I see data inside? When I go on previous page, data is there, so I'm guessing something in async world I'm doing wrong, but can't figure out what :/

here is my initialization in component.ts

  ngAfterViewInit() {

    this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);

    merge(this.sort.sortChange, this.paginator.page)
      .pipe(
        startWith({}),
        switchMap(() => {
          this.isLoadingResults = true;

          this.store.dispatch(new ListLoad(this.getQueryParams()));

          return this.store.pipe(select('list'));
         }),
        map((state: State) => {
          this.isLoadingResults = false;
          this.resultsLength = state.totalRecords;

          return this.cacheKey in state.data ? state.data[this.cacheKey] : [];
        }),
        catchError((err) => {
          this.isLoadingResults = false;
          this.resultsLength = 0;

          return observableOf([]);
        })
      )
      .subscribe((data: any[]) => {
        return this.data = data
      });
  }

and here is my effect definition effects.ts

@Effect()
loadData = this.actions$.pipe(
    ofType(actions.actionTypes.ListLoad),
    mergeMap((action: actions.actionTypes.ListLoadSuccess) => this.service.getAll(action.payload).pipe(
        map(
            response => {
                let apiResponse = new ApiResponse(response);
                let cacheKey = JSON.stringify(action.payload);

                return apiResponse.isSuccess ?
                    new actions.ListLoadSuccess({ key: cacheKey, data: apiResponse.data }) :
                    new actions.ListLoadFailed(`code: ${apiResponse.status.error_code}; message: ${apiResponse.status.error_message}`);
            }
        ),
        catchError(err => observableOf(new actions.ListLoadFailed(err)))
    ))
)

In addition to this, I would like to cancel http request, if page containing the data is present in NgRx store


Solution

  • I was able to resolve it. Issue was that I was updating property of store which is object, by adding new property to it. Store does not emit event that fragment is updated, so Select subscription is not triggered. I've introduced another boolean param for loading state, which I listen for changes, and if loading is false (page is loaded), I select desired fragment. I've also added extra code for page caching

    component.ts

      ngOnInit() {
    
        this.isLoadingResults$ = this.store.pipe(
          select(state => state.stateFragment.isListLoading),
          takeWhile(() => this.componentActive) //unsubscribe
        );
    
        this.store.dispatch(new ListLoad());
    
        this.isLoadingResults$.pipe(
          filter((isLoading:boolean) => !isLoading),
          switchMap(() => this.store.pipe(
            select(state => state.stateFragment),
            takeWhile(() => this.componentActive) //unsubscribe
          )),
          map(...)
        ).subscribe(...);
    
        //Other stuff here
    
      }
    

    effects.ts

    @Effect()
        load$ = this.actions$.pipe(
            ofType(actions.actionTypes.ListLoad),
            withLatestFrom(this.store.pipe(select(state.stateFragment))),
            filter(([action, store]) => {
                let isPageCached: boolean = action.payload in store.stateFragment;
                if (isPageCached) {
                    this.store.dispatch(new actions.ListLoaded()); //for sake of changing loading state
                }
                return !isPageCached;
            }),
            switchMap(([action, store]) => {
                return this.service.getAll(action.payload).pipe(
                    map(
                        response => {
                            let apiResponse = new ApiResponse(response);
                            return apiResponse.isSuccess ?
                                new actions.ListLoadSuccess({ key: action.payload, data: apiResponse.getData(), totalRecords: apiResponse.getTotalCount() }) :
                                new actions.ListLoadFailed(`code: ${apiResponse.status.error_code}; message: ${apiResponse.status.error_message}`);
                        }
                    ),
                    catchError(err => observableOf(new actions.ListLoadFailed(err)))
                );
            }
            ), share()
        )
    

    reducer.ts

    export function reducer(state = initialState, action: Actions) {
        switch (action.type) {
            case actionTypes.ListLoad:
                return {
                    ...state,
                    isListLoading: true
                };
            case actionTypes.ListLoaded:
                return {
                    ...state,
                    isListLoading: false
                };
            case actionTypes.ListLoadSuccess:
                state.listData[action.payload.key] = action.payload.data;
    
                return {
                    ...state,
                    isListLoading: false,
                    listData: state.listData,
                    listTotal: action.payload.totalRecords
                };
            case actionTypes.ListLoadFailed:
                return {
                    ...state,
                    isListLoading: false,
                    error: action.payload
                };
            case actionTypes.ListClear:
                return {
                    ...state,
                    listData: {},
                    listTotal: 0
                };;
            default:
                return state;
        }
    }