web-dev-qa-db-fra.com

ngrx Charger les données du serveur uniquement si le magasin est vide

J'ai un ensemble statique de données, une liste de pays, qui sont utilisés sur certains composants. Ces données sont chargées sur la ngOnInit() de ces composants mais je voudrais les charger uniquement si c'est la toute première fois que je demande les données (le magasin est vide). Chaque fois que je charge le composant, j'aimerais simplement utiliser les données du magasin sans les "rafraîchir".

Comment est-ce réalisable en utilisant ngrx?

J'utilise des effets. Voici à quoi ressemble mon code:

Le composant:

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());
}

L'effet:

    @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)))
  );

Et le réducteur:

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
        });
    }, {});

Merci, Gab

14
gabric

Il existe différentes façons de procéder. Tout d'abord, vous pouvez garder un hasLoaded: boolean propriété en état. Ensuite, vous pouvez vérifier cela avant de faire appel au service.

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

Une autre option consiste à laisser votre @Effect vérifier la propriété hasLoaded:

@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)))
);

Pour que cela fonctionne, vous devez fournir le magasin dans votre constructeur d'effets.

9
Hetty de Vries

TL; DR

Utilisez l'opérateur take(1) dans l'effet

N'oubliez pas la gestion des erreurs en utilisant catchError et retournez EMPTY, sinon lorsqu'une erreur se produit, cette erreur sera toujours renvoyée (timeout, erreur d'authentification, hors ligne ...)


J'ai eu exactement le même cas que vous, ce que j'ai fait a été d'ajouter dans les effets l'opérateur rxjs take pour récupérer les pays uniquement la première fois que l'action LoadCountries a été envoyée.

@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)
  );

Retourner EMPTY à l'intérieur catchError terminera l'observable sans passer par la take(1)

Vous pouvez sélectionner les pays du magasin dans les effets, s'ils sont représentés dans le magasin, nous ignorons l'action, sinon nous récupérons les pays.

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

Pour plus d'informations, voir Commencez à utiliser ngrx/effects pour cela .

4
timdeschryver

Ma réponse est une variation de Hetty de Vries réponses. Si vous souhaitez simplement récupérer le contenu de la boutique sans toucher aux effets, vous pouvez faire quelque chose de similaire à ceci:

this.store.select(state => state.producer.id).forEach( id => {
...
}

en supposant que votre magasin contient un objet producer avec un attribut id.

0
Investigator