Thursday, March 25, 2021

NGRX Actions

 export interface Action { type: string }

This is what defines an Action. 

Ngrx implements a message passing architecture, where Actions are dispatched.

this.store.dispatch(action)

The mechanism behind this is an ActionSubject, which is a BehaviorSubject with some extensions. When you dispatch an action, it is as simple as this.

this.actionsObserver.next(action);

The listeners subscribe to this action stream, either in the reducers which modify the state, or an Effect. This simple structure allows you to build a message passing system which define the data flows in your application.

Here is a list of some things you need to know:

A well designed message passing system will clearly define the paths of execution and transfer of data. I find the easiest way of seeing this in my applications is to log out the action from a reducer.

export function reducer(state: State | undefined, action: Action) {
  console.log(action.type);
  return emailReducer(state, action);
}

You will see the flow of commands and data as your application goes through it's function. 

Broadly, there are two types of Actions. Those that the reducers listen for to modify the state, and those that don't, and are listened for in Effects. Reducers are pure functions, and when something asynchronous or a side effect needs to occur, the Effect will listen. The typical example goes like this

  1. Component dispatches an action to load data.
  2. an Effect listens for the load action, and does an api call to fetch the data.
  3. the Effect emits an action with the data attached.
  4. the reducer listens for that action and updates the state.
Message passing systems can easily get out of hand. I think the key is to use a declarative approach. An example of a declarative system that easily translates into actions is a file upload component that I wrote. It has the input to select a file or files, a table to contain the list, and buttons to upload them individually or all of them.
  • selected file or files are inserted in the state
  • a file or all files can be removed from the state
  • a file or list of files are uploaded
  • the upload progress is displayed
  • success clears the state
  • an error sets the status
This is how it is translated into Actions

export const InsertFile = createAction(
  '[UploadTableComponent] Insert file',
  props<{ selectedfile: SelectedFileList }>()
);

export const RemoveFile = createAction(
  '[UploadTableComponent] Remove file',
  props<{ selectedfile: SelectedFileList }>()
);

export const SetFileStatus = createAction(
  '[SelectedFileList Effect] Set File Status',
  props<{ selectedfile: SelectedFileList, status: string }>()
);

export const SetFileProgressStatus = createAction(
  '[SelectedFileList Effect] Set File progress Status',
  props<{ selectedfile: SelectedFileList, progress: number }>()
);

export const SetFileProgressComplete = createAction(
  '[SelectedFileList Effect] Set File progress Complete',
  props<{ selectedfile: SelectedFileList }>()
);

export const SelectAllFiles = createAction(
  '[UploadTableComponent] Select All',
  props<{ id: string, module: string, selectall: boolean }>()
);

export const SelectFiles = createAction(
  '[UploadTableComponent] Select File',
  props<{ selectedfile: SelectedFileList }>()
);

export const UploadFiles = createAction(
  '[UploadTableComponent] Upload File',
  props<{ selectedfile: SelectedFileList }>()
);

export const UploadSelected = createAction(
  '[UploadTableComponent] Upload Selected',
  props<{ selectedfile: SelectedFileList, process: string }>()
);

In this component the file select input dispatches the InsertFile action. Buttons on the file list table can either remove or upload an individual file, or remove or upload the whole list. The httpclient emits actions to update the progress and completion of the upload, or notify of an error.

If you lay out a declarative description of your component, it makes the flow clear and understandable, and it is easy to translate into actions. All that is then required is to define the data that gets passed around. Then when the component gets executed, logging the actions will match the declarative description, or expose flaws in the implementation that can be fixed.

The goal of the NGRX pattern is to make complicated data flows and interactions easy to understand. Self documenting Actions, along with a well thought out declarative spec for the flows and interactions will lead to a successful implementation.

Comments: Post a Comment

Subscribe to Post Comments [Atom]





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]