Why Should You Be Using Redux With Angular?
Whether your a beginner or seasoned user of Redux this article has something for you. Read on to learn about Redux and how to integrate it into your Angular application.
Join the DZone community and get the full member experience.
Join For FreeQuick Introduction: How to Implement Redux Into an Angular App
Redux is one of the coolest technologies in the front end development world today. Nevertheless, many people have no clue how to incorporate it into their applications or even why they should. In the first place, this post is a simple introduction to Redux. Also, we want to share how it can be added to an Angular application. Additionally, we will give some real examples of how Redux can be used to help you understand the value of this savvy technology in a simple and practical way!
Redux: One of the Hottest Libraries for Front end
In the case you’ve never heard of Redux, you may be wondering what exactly it is and why you should use it. Redux is one the hottest libraries for front end out there. Therefore, it is gaining more and more traction from developers in the industry every day, and transforming the way applications are built. At the same time, it enables a new way to conceive applications by using an unidirectional dataflow. As a result, this gives you better control over your application state and tools to eradicate issues associated with data mutability.
According to Redux's official documentation, it is “a predictable state container for JavaScript apps.” In fact, it is an incredibly lightweight (2kB, including dependencies) implementation of Flux.
What Is Flux?
Well, Flux is a data flow architecture created by Facebook back in 2014. Although it was meant to be used by ReactJS applications, it’s actually technology agnostic. Back then, the main purpose was to replace the bidirectional data flow that traditional architectures like MVC encourage for a unidirectional one. If you have ever been in a chaotic apocalyptic program, trying to figure out which view is updating a particular model using getters and setters, then you can probably understand why having a unidirectional dataflow is both convenient and beneficial.
In Flux, a dispatcher sends actions to stores and updates views in a unidirectional data flow. Therefore, it makes debugging easier and drastically reduces the probability of introducing bugs caused by cascading data changes.
Indeed, Redux implements Flux concepts, but also has some differences:
- Single source of truth, meaning the entire application state will be held in one and one place only.
- The immutability of data.
- There are no dispatchers.
Redux and Flux
Moreover, you can think of a Redux’s store as a tree, where you can’t change a node because data is immutable. So you’ll need to create a copy of the last node and perform your changes there. By doing this, you can always know the different states your application has been through and implementing features, like time traveling, becomes a breeze. This approach also favors debugging and testing, since you can isolate actions and debug/test them separately. In addition, the immutability of data enables the use of an invaluable tool called Redux Devtools, which provides time traveling, debugging functionality, and other great features to accurately observe the state of your applications.
Notably, the Redux cycle is really simple, as expressed in the following diagram:
The state of the application is kept in a data store, which provides information to be rendered by views. When a view triggers an action, this action is processed by a reducer. As a consequence, this generates a new state that updates the application state. For this reason views observing the store are notified of the change and are updated accordingly.
So Redux is simply based on actions, reducers, and a store. Let’s go a bit deeper into each.
Actions
Actions are payloads of information, that send data to the store. They are the store’s only source of truth. Also, they have a type. Redux only enforces the “type” property, the structure of the action can be whatever works for you and your application.
To make the process of dispatching actions easier, some special functions, called “Action Creators,” are commonly used. Actions creators are exactly as they sound: functions that create actions.
exportfunction addTodo(payload){
return{ type: ADD_TODO, payload }
}
Reducers
As actions only describe that “something” happened, reducers are the ones to specify how the application state changes in response to those actions.
Actually, Reducers are pure functions, meaning they cannot modify input data or be altered by any external state like databases, document object model events, global constants, etc.
They have the following signature:
(previousState, action)=> newState
In effect, the reducer receives the current state and an action to perform and, using the action’s payload, it generates a new state that updates the application’s state.
Also, a reducer is commonly handled with a switch statement, to act according to the received action:
function todoReducer(state = initialState, action){
switch(action.type){
case DO_SOMETHING:
returnObject.assign({}, state,{
counter: action.counter +1
})
default:
return state
}
}
In short, it is important to provide an implementation for the default scenario, in case an incorrect action is passed. The most important thing to take into account here is that the provided state can not be mutated. In order to ensure that, some people prefer to use some persistent data structures, provided by libraries like Immutable.js. However, these are often verbose. So it is easier to achieve the goal by using some constructors provided by ECMAScript 6 such as Object.assign() and spread operators.
Store
Last but not least, there’s the store. The store is the one that brings actions and reducers together. That means it:
- Holds the application state.
- Allows it to access that info through the getState() function.
- Allows the state to update with the dispatch() method.
- Registers listeners with the subscribe (listener) function.
- Unregisters them as well, using the object returned by the register function.
Let’s Practice: Integrating Redux to an Angular Application
As said before, Redux can be used with any JavaScript technology. So, enough talking!
To begin with, you can install the Redux library directly into your application. However, it might be useful to install a custom implementation for Angular which will give you access to some angular features like Dependency Injection and Observables. In order to do so, there are some good implementations out there such as @ngrx/store and @angular-redux/store.
Let’s choose @ngrx/store since it seems more active on GitHub.
Once you set a basic Angular application, install @ngrx/store with yarn (or npm, if you prefer):
yarn add @ngrx/core@ngrx/store
Define the application state models:
exportinterfaceIAppState {
calculator: ICalculatorState;
}
exportinterfaceICalculatorState {
result: number;
resetCounter: number;
}
Generate the calculator’s action creators:
import {
Injectable
} from '@angular/core';
@Injectable()
exportdefaultclassCalculatorActions {
publicstaticreadonly ADD = 'ADD';
publicstaticreadonly RESET = 'RESET';
public add = (payload: number) => ({
type: CalculatorActions.ADD,
payload
})
public reset = () => ({
type: CalculatorActions.RESET
})
}
In time, add the reducer:
import {
Action
} from '@ngrx/store';
import {
ICalculatorState
} from 'app.models';
const defaultState: ICalculatorState = {
result: 0,
resetCounter: 0
};
exportfunction calculatorReducer(state: ICalculatorState = defaultState, action: Action): number {
switch (action.type) {
caseCalculatorActions.ADD:
returnObject.assign({}, state, {
result: state.result + action.payload
});
caseCalculatorActions.RESET:
returnObject.assign({}, state, {
resetCounter: state.resetCounter + 1
});
default:
return state;
}
}
Then, import the Store Module into our app’s main module:
import {
NgModule
} from '@angular/core'
import {
StoreModule
} from '@ngrx/store';
import {
calculatorReducer
} from './calculator.reducer';
import CalculatorComponent from './calculator.component';
@NgModule({
imports: [ BrowserModule, StoreModule.forRoot({
calculator: calculatorReducer
}) ],
declarations: [
CalculatorComponent
]
})
exportclass AppModule {}
Finally, inject the “Store” service into our components and modules and request data from the state using the “store.select.”:
import {
Store
} from '@ngrx/store';
import {
CalculatorActions
} from './calculator.actions';
@Component({
selector: 'calculator',
template: `
<inputtype="number" #number/>
<button (click)="add(number.value)">Add</button>
<button (click)="reset()">Reset</button>
<p>{{result|async}}</p>
`
})
exportdefaultclassCalculatorComponent {
public result: Observable < number > ;
constructor(private store: Store < AppState > ,
private calculatorActions: CalculatorActions
) {
this.result = store.select('result');
}
public add(value: number): void {
this.store.dispatch(this.calculatorActions.add(value));
}
public reset(): void {
this.store.dispatch(this.calculatorActions.reset());
}
}
That’s all that is needed to set up Redux in a sample Angular application.
In Conclusion
To sum up, this post was only meant to be a very simple introduction to Redux and how it can be used with Angular. There are some important concepts to consider, such as dealing with async behaviors, an area where Redux fails to deliver a solution. But some libraries will need to be installed such as @ngrx/effects or redux-observable to get the job done. Additionally, some libraries to consider are redux-store and redux-devtools. For a more extensive list of helpful resources for Redux to deal with forms and many other scenarios, check out the awesome-redux page.
References
- Redux Official Page
- ReactJS GitHub Page
- NGRX Store
Opinions expressed by DZone contributors are their own.
Comments