web-dev-qa-db-fra.com

obtenir un seul élément de ngrx / store

J'ai écrit le réducteur suivant pour stocker les éléments d'état dans mon Angular 2. Les articles sont des offres de prix pour les instruments financiers (par exemple, les actions/devises).

Ma mise en œuvre de Reducer est la suivante:

export const offersStore = (state = new Array<Offer>(), action:Action) => {
switch(action.type){

    case "Insert":
        return [
            ...state, action.payload
        ];
    case "Update":
            return state.map(offer => {
                    if(offer.Instrument === action.payload.Instrument)
                    {
                        return Object.assign({}, action.payload);
                    }
                    else return offer;
            });
    case "Delete":
        return state.filter(offer => offer.Instrument !== action.payload )
    default:
        return state;
    }

}

J'ai réussi à faire fonctionner les insertions, les mises à jour et les suppressions - même si ce n'était pas facile. Je trouve que Redux est en quelque sorte un changement de paradigme par rapport à la façon dont j'ai codé pendant des années.

J'ai un composant/page d'instrument sur mon application - qui montre toutes les informations disponibles pour un instrument spécifique, indiqué par InstrumentId, par exemple "EUR/USD" (stocké dans la propriété payload.Instrument).

Mon problème est que je ne sais pas comment rechercher efficacement un instrument spécifique et le sortir du magasin. Non seulement cela, mais je veux aussi que l'instrument que je récupère soit mis à jour si l'instrument dans le magasin est mis à jour car ils sont souvent via websocket Push depuis le serveur. J'ai donc vraiment besoin de rechercher dans le magasin un instrument spécifique, et de le renvoyer en tant qu'observable, qui continuera à mettre à jour le composant View en fonction des nouvelles données qui sont transmises au magasin.

Comment puis-je atteindre cet objectif?

15
reach4thelasers

Ce que vous n'obtenez pas, c'est que pour chaque action appelée sur un réducteur, le nouvel état est renvoyé.

D'après votre exemple de code dans la question, votre état n'est qu'une liste d'instruments.
Il n'y a pas d'index, donc la seule façon de vérifier si un instrument est dans la liste est de rechercher toute la liste.

Et si votre état était un dictionnaire? De plus, que se passe-t-il si vous gardez une liste d'index séparée du dictionnaire?

votre type d'état est le suivant:

export interface OfferState {
  ids: string[];
  entities: { [id: string]: IOffer };
};

Chaque fois qu'une action est exécutée, le nouvel état est renvoyé. C'est un concept important dans Redux, car l'état ne peut jamais être muté directement. En fait, il est préférable d'appliquer strictement cela lorsque vous composez votre réducteur: (disons que vous avez "un réducteur d'offres" et un autre réducteur, vous les combinez en un avec compose:

> export default compose(storeFreeze, combineReducers) ({   oether:
> otherReducer,   offers: offersReducer });

Il est facile de mal faire les choses dans Redux - mais l'utilisation de storeFreeze générera une erreur si vous essayez de muter l'état directement. Le fait est que les actions changent d'état et créent un nouvel état. Ils ne changent pas l'état existant - cela nous permet d'annuler/refaire ... etc.

En utilisant votre exemple ci-dessus, je l'utiliserais comme réducteur de mon offre:

export interface OfferState {
  ids: string[];
  entities: { [id: string]: IOffer };
};

export default function(state = initialState, action: Action): OfferState {
    switch(action.type){
        case OfferActions.INSERT:
        const offer : IOffer = action.payload;
        return {
            ids: [ ...state.ids, action.payload.Instrument ],
            entities: Object.assign({}, state.entities, { [action.payload.Instrument]: action.payload})
        };
        case OfferActions.UPDATE:
            return {
                ids: [...state.ids],
                entities: Object.assign({}, state.entities,  { [action.payload.Instrument]: action.payload})
            }

        default:
            return state;
    }
}

notez que des modifications sont apportées à un état temporaire via object.assign (copie complète), puis le nouvel état est renvoyé.

L'autre réponse à la question était un peu déroutante. Cela expliquait en détail comment combiner différents réducteurs, mais cela n'avait pas beaucoup de sens pour moi.

dans vos réducteurs/index.ts vous devriez avoir un type:

export interface AppState {
  otherReducer: OtherReducer;
  offers: fromOffersReducer.OfferState;
}

à l'intérieur de ce index.ts, vous devriez avoir des fonctions qui obtiennent les réducteurs:

export function getOfferState() {
  return (state$: Observable<AppState>) => state$
    .select(s => s.offers);
}

export function getOtherReducer() {
  return (state$ : Observable<AppState>) => state$
    .select(s => s.otherReducer)
}

à l'intérieur de notre offerReducer et de notre otherReducer, nous définissons des fonctions qui peuvent interroger les données dont nous avons besoin. Ce sont des fonctions anonymes, qui ne sont actuellement liées à rien, mais nous les lierons plus tard (aux getReducerFunctions).

exemples de ces fonctions:

export function getOfferEntities() {
  return (state$: Observable<OfferState>) => state$
    .select(s => s.entities);
};

export function getOffer(id: string) {
  return (state$: Observable<OfferState>) => state$
    .select(s => s.entities[id]);
}

cela ne fait rien. à moins que nous ne l'appliquions à certaines données utiles (par exemple, offresRedeucer) que nous avons rendues plus fiables, et que nous combinions les deux comme ceci:

import offersReducer, * as fromOffersReducer from './OffersReducer';

 export function getOfferEntities() {
   return compose(fromOffersReducer.getOfferEntities(), getOfferState());
 }

  export function getOffer(instrument:string) {
   return compose(fromOffersReducer.getOffer(instrument), getOfferState());
 }

J'ai essayé d'anticiper le prochain problème auquel vous serez confronté (un deuxième réducteur) ainsi que de répondre à la question "comment obtenez-vous un article?" comme je sais que vous allez tomber sur cette question à peu près immédiatement. Mais ne laissez pas cela vous confondre avec votre requête d'origine .... L'astuce pour trouver un élément, est de maintenir un état qui est une carte d'éléments (un dictionnaire) - sans jamais muter cet état et toujours renvoyer un nouvel état. Suivez ces instructions et votre problème est résolu. Le prochain problème auquel vous serez confronté est celui de plusieurs réducteurs, mais relisez ci-dessus et cela devrait avoir du sens.

Je ne suis pas d'accord avec la réponse précédente sur les questions de stackoverflow - ce sont tout ce dont vous avez besoin. Et si vous avez besoin d'un tutoriel, ils peuvent être un tutoriel. Et je ne pense pas que ce soit une mauvaise chose - en particulier dans des sujets de niche comme RXJS où peu de conseils et de documentation sont disponibles.

Merci d'avoir mis une prime sur cette question - j'espère que je vous ai donné une meilleure réponse. Toutes les autres questions - juste crier. Je suis heureux d'aider. Vous n'obtiendrez pas de ma part "Je suis réticent à répondre à plus de vos questions" ... Je suis là pour aider - pas pour insulter.

14
reach4thelasers

D'accord, je vais essayer d'expliquer une façon de mettre cela en place et, espérons-le, d'une manière que vous et les autres puissiez comprendre.

Donc, si vous stockez un tableau d'objets et que vous avez besoin d'accéder à une ligne par un certain identifiant ou clé, la façon dont l'exemple d'application ngrx vous montre que vous pouvez le faire est de créer un objet contenant votre objet qui utilisera (dans le cas de leur application de livre) l'ID du livre comme clé de propriété. Vous pouvez définir n'importe quelle chaîne comme nom de propriété. Supposons donc que vous ayez votre état avec une propriété appelée "entités" dont la valeur est un objet vide. Vous pouvez ensuite prendre votre tableau et créer des propriétés sur l'objet "entités".

export interface BooksState {
  entities: { [id: string]: Book };
};

Commençons par une seule ligne dans le tableau des objets du livre. Pour ajouter cette ligne à l'objet "entités" vous le faites comme ceci.

 reducerState.entities[book.id] = book;

Ce qui précède fera de l'id du livre une propriété sur l'objet "entités".
Si vous l'inspectiez dans la console ou dans les outils de débogage, cela pourrait ressembler à ceci après sa création.

 reducerState.entities.15: { id: 15, name: "To Kill a Mockingbird" }

L'exemple d'application ngrx fonctionne sur le tableau complet pour l'ajouter à l'état à l'aide de l'opérateur de réduction sur le tableau javascript.

   const newBookEntities 
              = newBooks.reduce(
                  (entities: { [id: string]: Book }, book: Book) => 
                           { 
                              return Object.assign(entities, { [book.id]: book }); 
                           }, {});

Et puis créez des fonctions spéciales pour obtenir des parties de cet état qui peuvent être composées plus tard pour obtenir les lignes spécifiques dont vous avez besoin

export function getBooksState() {
  return (state$: Observable<AppState>) => state$
    .select(s => s.books);
}


export function getBookEntities() {
  return (state$: Observable<BooksState>) => state$
   .select(s => s.entities);
};
export function getBooks(bookIds: string[]) {
  return (state$: Observable<BooksState>) => state$
              .let(getBookEntities())
              .map(entities => bookIds.map(id => entities[id]));
   }

Et ils les composent dans le baril index.ts dans l'exemple

import { compose } from '@ngrx/core/compose';


export function getBooks(bookIds: string[]) {
   return compose(fromBooks.getBooks(bookIds), getBooksState());
}

Cette fonction enchaîne les appels ensemble de droite à gauche. Appelez d'abord getBooksState pour obtenir l'état du réducteur puis appelez fromBooks.getBooks et transmettez le bookid que vous voulez et l'état du précédent "getBooksState" et vous permettant de faire le mappage vers les états sélectionnés dont vous avez besoin.

Dans votre composant, vous pouvez importer cette fonction et l'utiliser pour obtenir uniquement les livres que vous souhaitez.

myBooks$: Observable<Books>  =  this.store.let(getBooks(bookIds)));

Cet observable retourné est mis à jour à chaque mise à jour du magasin.

Ajouté: Donc l'opérateur "let" sur l'objet store passe dans l'observable courant en maintenant l'objet state du store à la fonction qui est retournée par le "getBooks" " une fonction.

Regardons cela de plus près.

export function getBooks(bookIds: string[]) {
   return compose(fromBooks.getBooks(bookIds), getBooksState());
}

Deux fonctions sont passées dans la fonction de composition ici - fromBooks.getBooks (bookIds) et getBooksState (). La fonction de composition permet de passer une valeur dans la première fonction de droite et de faire en sorte que les résultats des fonctions soient passés dans la fonction de gauche et ainsi de suite. Donc, dans ce cas, nous allons prendre les résultats de la fonction retournée par "getBooksState" et les passer dans la fonction retournée par la fonction "fromBooks.getBooks (bookIds)", puis retourner finalement ce résultat.

Ces deux fonctions prennent dans un observable. Dans le cas de la fonction retournée par getBooksState (), il prend en paramètre l'objet observable de l'état du magasin à partir duquel il va mapper/sélectionner (mapper et sélectionner sont des alias les uns pour les autres) à la tranche du magasin dont nous nous soucions et renvoyons le nouveau mappé observable. Cet observable mappé sera transmis à la fonction renvoyée par la fonction fromBooks.getBooks (bookIds). Cette fonction attend la tranche d'état observable. Cette fonction effectue le nouveau mappage en retirant uniquement les entités qui nous intéressent. Ce nouvel observable est ce qui est finalement renvoyé par l'appel "store.let".

Si vous avez besoin de plus de précisions, veuillez poser des questions et je ferai de mon mieux pour clarifier.

https://github.com/ngrx/example-app

8
wiredprogrammer