2

I am starting to look into ngrx/Store and I am facing the following issue.

From my UI component I need to execute a call to a remote rest API, which potentially can take some time to execute. I want to manage the wait-time showing the user a standard loading-spinner.

I am used to manage such situations with code that looks like

    let loading = this.loadingCtrl.create({
          content: 'Please wait while we load stuff'
        });
   loading.present();
   this.longRunningService.doHeavyWork()
          .subscribe(
              (result) => {
                              // manage result;
                            },
              (err) => console.log(err),
              () => loading.dismiss()
          );

The key point (for my problem) in the above code snippet is that when the longRunningService completes the doHeavyWork logic (i.e. when the Observable is completed) the loading spinner is dismissed.

Now I want to refactor this code and use ngrx/Store dispatch mechanism.

My question is how to manage the loading.dismiss() logic while using ngrx/Store dispatch mechanism. In other words I do not know how to close the loading-spinner (once the result has been received from the back end rest API) when using ngrx/Store dispatch Action mechanism.

Any advice is very much appreciated

Picci
  • 16,775
  • 13
  • 70
  • 113
  • 1
    Have a look at [this answer](http://stackoverflow.com/a/40496998/6680611) and have a look at [ngrx/effects](https://github.com/ngrx/effects) – cartant Jan 12 '17 at 22:42

1 Answers1

4

You need to add the ngrx/effects library to deal with side effects like the "longRunningService"

I learn this from the official example of ngrx. I recommend you to take a look at https://github.com/ngrx/example-app

The following code is based from the example-app and deal with the loading stuff, please check it.

I assume you are coding a ionic v2 app? because of the LoadingController..

Actions:

export const LOAD_STUFF = "LOAD_STUFF";
export const LOAD_STUFF_SUCCESS = "LOAD_STUFF_SUCCESS";
export const LOAD_STUFF_FAIL = "LOAD_STUFF_FAIL";

@Injectable()
export class StuffActions{

  loadStuff() : Action {
    return {
      type: LOAD_STUFF
    };
  }

  loadStuffSuccess(stuff : any []) : Action {
    return {
      type: LOAD_STUFF_SUCCESS,
      payload: stuff
    };
  }
}

Reducer

export interface State {
  entities: [];
  loading : boolean;
  complete : boolean;
}

const initalState : State = {
  entities: [];
  loading : false;
  complete : false;
};

export function reducer(state = initalState, action) : State {
  switch (action.type) {

    case LOAD_STUFF:
      return Object.assign({}, state, {
        loading: true,
        complete: false
      });

    case LOAD_STUFF_SUCCESS:
      return {
        complete: true,
        loading: false,
        entities : action.payload
      };

    default:
      return state;
  }
}

// SELECTORS
export const getLoading = (state: State) => state.loading;
export const getComplete = (state: State) => state.complete;
export const getEntities = (state: State) => state.stuff;

effects

@Injectable()
export class EnvioEffects {
  constructor(private actions$: Actions, private _stuffActions : StuffActions, private _longRunningService: LongRunningService ){}

  @Effect() loadStuff: Observable<Action> = this.actions$
    .ofType(LOAD_STUFF)
    .switchMap(() => this._longRunningService.getStuff())
    .map( stuff => this._stuffActions.loadStuffSuccess(stuff));

}

selectors index. For selectors I use https://github.com/reactjs/reselect

import * as fromStuff from "./stuff";
import {createSelector} from "reselect";
import {ActionReducer, combineReducers, Action} from "@ngrx/store";    

export interface State {
  stuff: fromStuff.State
}

const reducers = {
  stuff: fromStuff.reducer
};

export const reducer: ActionReducer<State> = combineReducers(reducers);

export const rootReducer = (state: State, action: Action) => {
  return reducer(state, action);
};

export const getStuffEntities = createSelector(getOriginState, fromStuff.getEntities);
export const getStuffLoading = createSelector(getOriginState, fromOrigin.getLoading);
export const getStuffComplete = createSelector(getOriginState, fromOrigin.getComplete);

Page

import * as fromRoot from "../../reducers/index";

@Component({
  selector: 'stuff',
  templateUrl: 'stuff.html'
})
export class StuffPage implements OnDestroy {

  destroy$: Subject<any> = new Subject();

  constructor(public navCtrl: NavController,
              public loadingCtrl: LoadingController,
              private _store: Store<fromRoot.State>,
              private _stuffActions: StuffActions) {
  }


  ionViewDidLoad() {
    // HERE I SHOW THE LOADING ANIMATION UNTIL THE LONG RUNNING SERVICE COMPLETE
    this._store.select(fromRoot.getStuffLoading)
      .takeUntil(this.destroy$)
      .filter(isloading => isloading)
      .map(() => this.createLoader())
      .do(loading => loading.present())
      .delayWhen(loading => this._store.select(fromRoot.getStuffComplete).filter(complete => complete))
      .map(loading => loading.dismiss())
      .subscribe();

    // LOAD THE STUFF
    this._store.dispatch(this._stuffActions.loadStuff());

  }

  createLoader(): Loading {
    return this.loadingCtrl.create({
      content: "Loading stuff"
    });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.unsubscribe();
  }
}
Victor Godoy
  • 1,642
  • 15
  • 18