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?
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.