3
votes

I'm working on a project using Angular 2 and @ngrx/store.question about a good practice and how to do this.

I've developed a "web finder" to manage files. So I can do some basic actions like copy, move etc.

I'll don't explain why because it's just an example but moving a file consists in 3 steps :

  • Logic: do some verifications.
  • dispatch an action A
  • dispatch an action B

Those 3 steps are done inside my angular component. I've thought about it and I think it's a better idea to simply dispatch one action whichdo some verifications and dispatches 2 other actions.

Why would I do this ? Firstly, it's easier to read

this._store.dispatch(copy(myFile));

rather than

// ...
// verifications
// ...

this._store.dispatch(action1(myFile, foo, bar));
this._store.dispatch(action2(myFile));

Secondly, I can dispatch my copy action creator on the fly without experiencing any side-effects because the verification will be executed inside the action.

Actually, I can dispatch my action1 et action2 without doing my verifications.

Finally, all my logic is inside my actions. I can keep my component simple and focus on their job (manage the UI etc). Which is great.

Question 1: Good practice ?

IIs it a good idea to do that ? I think so, but your experience interests me.

Question 2: How to ?

How to do that ? My actions creators are just functions. I can't decorate them with Injectable and use the constructor to inject instances.

Here is an example of action creators:

import { Action } from '@ngrx/store'

export const NAVIGATE_TO_NODE:string = '[Browser] Navigate to node';
export const ADD_TO_SELECTION:string = '[Browser] Add to browser selection';
export const REMOVE_FROM_SELECTION:string = '[Browser] Remove from browser selection';
export const REMOVE_ALL_FROM_SELECTION:string = '[Browser] Remove all from browser selection';

export const navigateToNode:(nodeId: number, paneId?: number)=>Action = (nodeId: number, paneId?: number) => {
  return {
    payload: { nodeId, paneId },
    type: NAVIGATE_TO_NODE
  };
};
export const addToSelection: (addInfo: any)=>Action = (addInfo: any) => {
  return {
    payload: addInfo,
    type: ADD_TO_SELECTION
  };
};
export const removeFromSelection: (removeInfo: any[])=>Action = (removeInfo: any[]) => {
  return {
    payload: removeInfo,
    type: REMOVE_FROM_SELECTION
  }
};
export const removeAllFromSelection: ()=>Action = () => {
  return {
    payload: null,
    type: REMOVE_ALL_FROM_SELECTION
  }
};

How can I dispatch from those action creators ?

I've already used redux as an implementation of Redux with React and I've founde a little package that allows me to dispatch from an action creator: redux-thunk

2

2 Answers

3
votes

I'll second wiredprogrammer's suggestion to look at the ngrx example app. It shows how to structure the actions, reducers and the effects.

Here is an example of the injectable action class.

@Injectable()
export class BookActions {
  static SEARCH = '[Book] Search';
  search(query: string): Action {
    return {
      type: BookActions.SEARCH,
      payload: query
    };
  }

Then in your component...

constructor(private store: Store<AppState>, private bookActions: BookActions) {}

search(query: SearchOutput) {
  this.store.dispatch(this.bookActions.search(query));
}

Approach it this way. Reducers should be pure functions with no side effects. So for example, to move a file you could dispatch an action that initiates the transaction, which could set the state with name of file, destination, and any other operational details required. The function would return the new state. Call this reducer START_MOVE.

In your effects, watch for START_MOVE, get the state which contains the transaction details, then do the first part of the transaction. When it is done, dispatch an action with the updated state to MOVE_2, or something more descriptive.

This reducer sets the state with details needed for the next step, returns it.

The effects watches for MOVE_2, then does the second stage of the transaction. When done, dispatch MOVE_3. Which as a function simply sets the state up for the third part, and the effects gets the details and finishes the transaction. When done, dispatch MOVE_SUCCESS which sets up the state with the new file names or whatever.

Any point in the process if there is an error a MOVE_ERROR can be dispatched, and handled in a similar way doing a cleanup or return to the initial state. Or MOVE_CANCEL if desired.

A component can observe the state and update the status to the user.

2
votes

Sorry for being brief but I've only skimmed your question and this is a response to only part of it. If you want to make your action creators injectable then wrap them in a class. You can get some excellent ideas on structure from the ngrx example app linked off the readme for the ngrx/store. I'm creating a project where I've mimicked their setup for action creators.

https://github.com/ngrx/example-app/tree/master/src/actions