Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ngrx Load data from server only if the store is empty

I have a static set of data, a list of countries, that are used on some components. This data is loaded upon the ngOnInit() of these components but I'd like to load them only if it's the very first time that I request the data (the store is empty). Every subsequent times I load the component I'd like to simply use the data from the store without "refreshing" it.

How is this achievable using ngrx?

I'm using Effects. This is how my code looks like:

The component:

export class EditPageComponent implements OnInit {
countries$: Observable<Country[]>

constructor(private store: Store<fromContacts.State>) {
    this.countries$ = store.select(fromContacts.getCountriesEntities);
}
ngOnInit() {
   this.store.dispatch(new countries.Load());
}

The Effect:

    @Effect()
      loadCollection$: Observable<Action> = this.actions$
        .ofType(countries.LOAD)
        .switchMap(() =>
          this.countriesService
        .getCountries()
        .map((countriesList: Country[]) => {
          return new countries.LoadSuccess(countriesList);
        })
        .catch(error => of(new countries.LoadFail(error)))
  );

And the reducer:

case countries.LOAD_SUCCESS: {

      const countriesList: Country[] = action.payload;
      const reducedCountries: { [id: string]: Country } = countriesList.reduce((countrs: { [id: string]: Country }, countr: Country) => {
        return Object.assign(countrs, {
            [countr.code]: countr
        });
    }, {});

Thanks, Gab

like image 545
gabric Avatar asked Oct 12 '17 08:10

gabric


People also ask

Where does NgRx store its data?

Where Does NgRx Store Data? NgRx stores the application state in an RxJS observable inside an Angular service called Store. At the same time, this service implements the Observable interface.

Is NgRx store persistent?

When you install the Redux DevTools addon in your browser while instrumenting your store with @ngrx/store-devtools you'll be able to persist the state and action history between page reloads. You can't really ask your users to install a browser extension.

How do I clean my NgRx store?

type = 'CLEAR' ONCE, it will clear all the properties of the store.

What is the advantage of NgRx store?

Ngrx/Store implements the Redux pattern using the well-known RxJS observables of Angular 2. It provides several advantages by simplifying your application state to plain objects, enforcing unidirectional data flow, and more.


3 Answers

There are different ways of doing this. First of all you can keep a hasLoaded: boolean property in the state. Then you can check this before you make the service get call.

ngOnInit() {
  this.store.select(getHasLoaded)
    .take(1)
    .subscribe(hasLoaded => {
      if (!hasLoaded) this.store.dispatch(new countries.Load()); 
    }
}

Another option is to let your @Effect check the hasLoaded property:

@Effect()
  loadCollection$: Observable<Action> = this.actions$
    .ofType(countries.LOAD)
    .withLatestFrom(this.store.select(getHasLoaded)
    .filter(([ action, hasLoaded ]) => !hasLoaded) // only continue if hasLoaded is false 
    .switchMap(() =>
      this.countriesService
        .getCountries()
        .map((countriesList: Country[]) => {
          return new countries.LoadSuccess(countriesList);
        })
    .catch(error => of(new countries.LoadFail(error)))
);

For this to work you need to provide the store in your Effects constructor.

like image 161
Hetty de Vries Avatar answered Oct 19 '22 13:10

Hetty de Vries


TL;DR

Use take(1) operator in the effect

Don't forget the error handling using catchError and return EMPTY, otherwise when an error occurs, that error will be returned always (timeout, auth error, offline...)


I had exactly the same case as you, what I did was adding in the effects the rxjs operator take to fetch the countries only the first time the LoadCountries action was dispatched.

@Effect()
  loadCountries$: Observable<CoreActions> = this.actions$.pipe(
    ofType(CoreActionTypes.LoadCountries),
    mergeMap(() =>
      this.countriesService.getAllCountries().pipe(
        map(c => new LoadCountriesSuccess(c)),
        catchError(() => {
          this.store.dispatch(new LoadCountriesFailed());
          return EMPTY;
        })
      )
    ),
    take(1)
  );

Returning EMPTY inside catchError will complete the observable without passing through the take(1)

like image 38
Yossely Mendoza Meneses Avatar answered Oct 19 '22 13:10

Yossely Mendoza Meneses


You can select the countries from the store within effects, if they are represented in the store we ignore the action, if not we fetch the countries.

@Effect()
getOrder = this.actions.pipe(
  ofType<GetOrder>(ActionTypes.GetOrder),
  withLatestFrom(this.store.pipe(select(getOrders))),
  filter(([{payload}, orders]) => !!orders[payload.orderId])
  mergeMap([{payload}] => {
    ...
  })
)

For more info see Start using ngrx/effects for this.

like image 12
timdeschryver Avatar answered Oct 19 '22 13:10

timdeschryver