web-dev-qa-db-fra.com

Comment utiliser un autre service Angular2 dans un réducteur ngrx/Store?

Nouveau chez ngrx/Store et réducteur. En gros, j'ai ce réducteur:

import {StoreData, INITIAL_STORE_DATA} from "../store-data";
import {Action} from "@ngrx/store";
import {
  USER_THREADS_LOADED_ACTION, UserThreadsLoadedAction, SEND_NEW_MESSAGE_ACTION,
  SendNewMessageAction
} from "../actions";
import * as _ from "lodash";
import {Message} from "../../shared-vh/model/message";
import {ThreadsService} from "../../shared-vh/services/threads.service";

export function storeData(state: StoreData = INITIAL_STORE_DATA, action: Action): StoreData {


  switch (action.type) {

    case SEND_NEW_MESSAGE_ACTION:
      return handleSendNewMessageAction(state, action);

    default:
      return state
  }
}

function handleSendNewMessageAction(state:StoreData, action:SendNewMessageAction): StoreData {

  const newStoreData = _.cloneDeep(state);

  const currentThread = newStoreData.threads[action.payload.threadId];

  const newMessage: Message = {
    text: action.payload.text,
    threadId: action.payload.threadId,
    timestamp: new Date().getTime(),
    participantId: action.payload.participantId,
    id: [need a function from this service: ThreadsService]
  }

  currentThread.messageIds.Push(newMessage.id);

  newStoreData.messages[newMessage.id] = newMessage;

  return newStoreData;
}

Le problème est dans la fonction réducteur, je ne sais pas comment injecter un service injectable que j'ai créé dans un fichier différent et utiliser la fonction qu'il contient. La partie id - J'ai besoin de générer un identifiant Pushbase à l'aide d'une fonction comme this.threadService.generateID () ... 

Mais puisqu'il s'agit d'une fonction, je n'ai pas de constructeur pour utiliser DI et je ne sais pas comment obtenir des fonctions dans threadService!

15
Hugh Hou

Il n’existe aucun mécanisme permettant d’injecter des services dans les réducteurs. Les réducteurs sont supposés être des fonctions pures.

Au lieu de cela, vous devriez utiliser ngrx/effects - qui est le mécanisme permettant de mettre en œuvre des effets secondaires d’action. Les effets écoutent des actions particulières, effectuent des effets secondaires, puis (éventuellement) émettent d'autres actions.

En règle générale, vous divisez votre action en trois: la demande; la réponse de réussite; et la réponse d'erreur. Par exemple, vous pourriez utiliser:

SEND_NEW_MESSAGE_REQ_ACTION
SEND_NEW_MESSAGE_RES_ACTION
SEND_NEW_MESSAGE_ERR_ACTION

Et votre effet ressemblerait à quelque chose comme ça:

import { Injectable } from "@angular/core";
import { Actions, Effect, toPayload } from "@ngrx/effects";
import { Action } from "@ngrx/store";
import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/map";

@Injectable()
export class ThreadEffects {

  constructor(
    private actions: Actions,
    private service: ThreadsService
  ) {}

  @Effect()
  sendNewMessage(): Observable<Action> {

    return this.actions
      .ofType(SEND_NEW_MESSAGE_REQ_ACTION)
      .map(toPayload)
      .map(payload => {
        try {
          return {
              type: SEND_NEW_MESSAGE_RES_ACTION,
              payload: {
                  id: service.someFunction(),
                  // ...
              }
          };
        } catch (error) {
          return {
              type: SEND_NEW_MESSAGE_ERR_ACTION
              payload: {
                error: error.toString(),
                // ...
              }
          };
        }
      });
  }
}

Plutôt que d'interagir avec le service, votre réducteur serait alors une fonction pure qui n'aurait plus qu'à gérer les SEND_NEW_MESSAGE_RES_ACTION et SEND_NEW_MESSAGE_ERR_ACTION pour faire quelque chose de approprié avec les charges utiles de succès ou d'erreur.

Les effets sont basés sur l'observable. L'intégration de services synchrones, basés sur les promesses ou observables est donc simple.

Il y a quelques effets dans le ngrx/example-app.

Concernant vos questions dans les commentaires:

La .map(toPayload) est juste pour la commodité. toPayload est une fonction ngrx qui existe et qui peut donc être passée à .map pour extraire l'action payload, c'est tout.

L'appel d'un service basé sur l'observable est simple. Typiquement, vous feriez quelque chose comme ça:

import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/of";
import "rxjs/add/operator/catch";
import "rxjs/add/operator/map";
import "rxjs/add/operator/switchMap";

@Effect()
sendNewMessage(): Observable<Action> {

  return this.actions
    .ofType(SEND_NEW_MESSAGE_REQ_ACTION)
    .map(toPayload)
    .switchMap(payload => service.someFunctionReturningObservable(payload)
      .map(result => {
        type: SEND_NEW_MESSAGE_RES_ACTION,
        payload: {
          id: result.id,
          // ...
        }
      })
      .catch(error => Observable.of({
        type: SEND_NEW_MESSAGE_ERR_ACTION
        payload: {
          error: error.toString(),
          // ...
        }
      }))
    );
}

De plus, les effets peuvent être déclarés en tant que fonctions renvoyant Observable<Action> ou en tant que propriétés de type Observable<Action>. Si vous regardez d'autres exemples, vous rencontrerez probablement les deux formes.

23
cartant

Après avoir réfléchi un peu à ce sujet, j'ai eu cette idée: que se passe-t-il si j'ai un service plein de fonctions pures que je ne souhaite pas conserver dans une variable globale en dehors de angular, comme ceci: 

export const fooBarService= {
    mapFooToBar: (foos: Foo[]): Bar[] => {
        let bars: Bar[];
        // Implementation here ...
        return bars;
    } 
}

Je voudrais l'avoir en tant que service afin que je puisse facilement le passer dans l'application sans que personne ne panique que je n'utilise pas l'injection de dépendance:

@Injectable()
export class FooBarService{
    public mapFooToBar (foos: Foo[]): Bar[] {
        let bars: Bar[];
        // Implementation here ...
        return bars;
    } 
}

Je peux utiliser ReflectiveInjector afin d'obtenir une instance du service dont j'ai besoin. Gardez à l'esprit que cet injecteur est appelé avant la mise en production de l'application principale, il est donc vraiment nécessaire de jouer à Nice et d'éviter de conserver l'état dans ces services. Et bien sûr aussi parce que les réducteurs doivent vraiment être purs (pour votre propre santé mentale).

// <!> Play Nice and use only services containing pure functions
var injector = ReflectiveInjector.resolveAndCreate([FooBarService]);
var fooBarService= injector.get(FooBarService);

// Due to changes in ngrx4 we need to define our own action with payload
export interface PayloadAction extends Action {
    payload: any
}

/**
 * Foo bar reducer
 */
export function fooBarReducer(
    state: FooBarState = initialState.fooBar, 
    action: PayloadAction
) {
    switch (action.type) {

        case fooBarActions.GET_FOOS_SUCCESS:
            return Object.assign({}, state, <FooBarState>{
                foos: action.payload,
                // No effects used, all nicelly done in the reducer in one shot
                bars: fooBarService.mapFooToBar (action.payload) 

            });

        default:
            return state;
    }

}

En utilisant cette configuration, je peux utiliser trois types de services FooBarDataService, FooBarMapsService et FooBarLogicService. Le service de données appelle le webapi et fournit les résultats observables à partir du magasin d'état. Le service de carte est utilisé pour mapper les foos aux barres et le service de logique pour ajouter la logique métier dans une couche séparée. De cette façon, je peux avoir de minuscules contrôleurs utilisés uniquement pour coller des objets et les servir aux modèles. Presque pas de logique dans les contrôleurs. Enfin, les résolveurs peuvent fournir les données du magasin d’état dans les itinéraires, éliminant ainsi complètement le magasin d’état.

Plus de détails sur ReflexiveInjectorhere .

1
Adrian Moisa