angularngrxngrx-effectsngrx-store-4.0

What is the use of services if you use the ngrx/store and effects?


I'm trying to add NGRX to my Angular project, but I can't tell if I still need services since components can dispatch actions. Does this mean I don't have to use any service other than ngrx/store?


Solution

  • I still use services to encapsulate logic. Generally, the components talk to the services and the services talk to the store.

    I have found the scope of what a service does goes beyond just storing, so removing them would put too many responsibilities / business logic into the store components.

    My store is focused on simple api-like actions, which are short and easy to test, e.g

      static INITIALIZE_CONFIG_REQUEST = 'INITIALIZE_CONFIG_REQUEST';
      static INITIALIZE_CONFIG_SUCCESS = 'INITIALIZE_CONFIG_SUCCESS';
      static INITIALIZE_CONFIG_FAILED  = 'INITIALIZE_CONFIG_FAILED';
      static INITIALIZE_CONFIG_TEMPLATE_ERROR  = 'INITIALIZE_CONFIG_TEMPLATE_ERROR';
    
      createInitializeConfigRequest() { // separate from invoking call for easier testing
        return {
          type: ConfigActions.INITIALIZE_CONFIG_REQUEST,
          httpRequest: {
            url: 'api/path/to/config',
            successAction: this.createInitializeConfigSuccess,
            failedAction: this.createInitializeConfigFailed,
            validateResponse: (data) => this.checkTemplate(data)
          }
        };
      }
      initializeConfigRequest() {  // called by config.service
        this.store.dispatch( this.createInitializeConfigRequest() );
      }
    
      createInitializeConfigSuccess(data) {
        const payload = data;
        return {
          type: ConfigActions.INITIALIZE_CONFIG_SUCCESS,
          payload
        };
      }
    
      createInitializeConfigFailed(error) {
        return {
          type: ConfigActions.INITIALIZE_CONFIG_FAILED,
          payload: {
            error
          }
        };
      }
    

    With this pattern, I can more easily add middleware such as that handling httpRequest and Response validation above.

    Most reductions are done via a single generic reducer which makes the assumption that payload properties correspond exactly to store properties. It's my favorite convention, because now I have one set of comprehensive tests for the generic reducer instead of n repetitive tests for n reducers.

      genericActionHandler(state, action) {
        if (!action.payload) {
          return state;
        }
        const newState = {...state};
        Object.assign(newState, action.payload);
        return newState;
      }
    

    Hope that gives you a feel for my architecture.