Angular is a massive user interface framework. It is a highly opinionated and comprehensive solution to many of the challenges of constructing modern user interfaces, particularly within a web browser. While Angular does a lot, when discussing Angular with respect to Redux, it becomes essential to boil Angular down to its primary purpose, and how Redux fits into the overall Angular picture. At its core, Angular is primarily focused on updating the UI based upon application state (sometimes referred to as the model, but the term “model” does not capture the full essence of the data powering the UI, whereas the term “application state” truly captures the full essence: user data and UI data). If Angular is massive, then Redux is the exact opposite but no less important. Redux is a small library focused on one small but critical task: managing application state. To put it another way, Redux manages the application state which is used by Angular to update the UI.
Does Redux require Angular? No. Commonly, Redux is used with Angular’s competitor, React. Truly, Redux is un-opinionated about which UI framework or library is used. Does Angular need Redux? No. Angular is essentially a complete solution. But while neither needs the other, both are better tools when used together to build next generation web applications, even universal applications. Universal applications run on any platform, not just a traditional browser-based web application.
Within the context of this post, the following parts of Angular and Redux will be explained and related to each other: components, stores, and services.
Components are the Angular piece for displaying the user interface through the combination of a template with component data populated from application state. Stores are the Redux piece through which new application states are produced in response to actions triggered by asynchronous operations from the web browser such as clicking a button, receiving new data over a web socket, and other operations. Services are the Angular part for connecting many components to a single store, so that when a new application state is produced, the components can process the new state and update the UI accordingly.
When using Redux, the source of model updates will be from the Redux store and the application state that the store itself manages. Usually, the source of actions dispatched to the Redux store will be from events triggered from the component, such as clicking buttons or AJAX calls triggered from the component. Typically, components will interact with the Redux store through a service (to be further discussed later) and they will trigger actions, and subscribe for new application states.
Creating a Redux store requires several parts. First, a reducer function needs to be created that will produce a new state given a current state and action. Reducer functions are pure functions meaning that they can only use data passed in as arguments, they cannot modify their parameters, produce no side effects and only the data which is returned from the function is the result. Reducer functions cannot rely upon global variables or variables defined in surrounding lexical scopes. Reducer functions cannot mutate the current state to produce the new state. Also, reducer functions cannot perform operations such as REST service calls or other asynchronous operations. In addition, the result of the reducer function is either the returning of the new state object or the returning of the original state object. Redux stores can use one or many reducer functions where reducer functions operate on some portion of the state tree.
Optionally, the reducer function can initialize the state object on its initial call using ES2015 default parameter values. Within the reducer function, the type of the action is used to determine what operations on the state need to be performed. In addition to the action type, additional data values may be present on the action object and can be used to produce a new application state.
To ease the creation of actions, action creator functions can be created so that when actions are dispatched the action object structure is correctly configured for the data requirements of the action.
For each kind of reducer function, a TypeScript interface can be configured to define the shape of the action. The interface will extend the Action interface provided by Redux. For the color action, a color property is needed to specify the color with which the desired operation should be performed such as adding the color or remove the color from the list of colors.
TypeScript supports enums which work perfectly for enumerating the various action types supported in reducer functions used with the store. Finally, action creator functions such as addColorAction and removeColorAction simplify the process of the creating the action object by providing a function which specifies the type and accepts parameters for the additional data needed by the action.
To properly use the Redux store, an Angular service should be created which wraps the Redux store and allows it to be injected throughout the Angular application. Angular services are the mechanism by which Angular makes general program functionality available throughout the application. Services are the least “Angular” part of Angular. While components, directives, and pipes are all central to Angular’s template-driven approach to constructing user interfaces, services typically represent code such as business logic, data storage, wrappers for 3rd party libraries, etc… One aspect of services which is uniquely Angular is the use of dependency injection. Dependency injection allows a service to be registered with an Angular module or directives (including components) and then injected anywhere in the component tree where the service is needed. Among the many benefits of the injection system, one critical benefit is that it makes it easier to unit test Angular parts because mocked versions of services can be created and easily configured.
Once created and exported, the services need to be configured for the application. One approach to configuring the services is to use the application’s main AppModule.
The service class is imported into the AppModule file, and then registered via the providers option of the module. For this demonstration, a simple provider configuration is all that is required. For more complex service needs, there are many additional options which may be used.
Services can be injected into components, directives and other services through their constructors. When Angular instantiates one of these parts, it determines which services are needed and it attempts to inject them. The ability to inject them requires a configuration as discussed in the previous section. After the configuration is complete, then services are injected via the component’s class constructor.
The use of the private access modifier to decorate the constructor parameter is a TypeScript shortcut which will create a private field on the component which will be initialized with the service passed in.
Changes to the application state are triggered by dispatching actions to the store. The store processes the action using the reducer functions registered with the store.
To dispatch the action, functions are available on the AppStore service which will dispatch an action using the store. To add a new color, the addColor function accepts the new color, then dispatches a new color action configured with the new color. The store processes the action and the reducer function produces a new state object with a new colors array which includes the original colors plus the new color added to the end. Once the state is updated, the store notifies subscribers that a new state is available.
A similar action to remove colors has been configured on the AppState service as well. Each action in the system needs to be similarly configured and processed by a reducer function. For large applications, many reducer functions may be required, and can be configured with the combineReducers function available from Redux.
When the component was originally initialized, the component registered a callback function with the store. The notification of the new application state will invoke this function thereby permitting the component’s callback function to retrieve the updated state and update its local component data which is then used to update the user interface.
As part of the subscription process, an unsubscribe function is returned. The unsubscribe function reference is stored on the component instance where it can be executed with the component is destroyed. Unsubscribing the callback function is essential to preventing memory leaks and unintentional callback execution on the component that has been destroyed.
With the subscription setup and tear down process coded into the component, the application is complete. Actions can be triggered and dispatched by the component, the Redux store updates the application state, and the application state is then consumed by the component to update the UI. A complete solution indeed. This solution can be expanded to support many more actions, reducers, larger state tree and asynchronous operations.
If you are interested in Redux or React training or consulting, please send us a message here.