30. Angular Redux
- State Management
- Build Medium to Large Applications with complex views and data flows
- Options: Angular Redux, NgrX, Angular Shared Services, MobX, Socket IO Node Server implementation
Redux:
- Store: JavaScript Object contains state of application, local client side database
- Actions JavaScript Object represents something has happened, semantically events
- Reducers Functions specify how state changes in response to an action, does not modify state returns a new state, pure functions, given same input always get same output, no side effects, immutable
Pure Functions:
- Easy to test
- Implement Undo and Redo
- Time travel debugging
Install in Terminal:
npm i redux @angular-redux/store --save
Actions:
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
Store:
import { INCREMENT, DECREMENT } from './actions';
export interface IAppState {
counter: number;
}
export const INITIAL_STATE: IAppState = {
counter: 0
};
export function rootReducer(state: IAppState, action): IAppState {
switch (action.type) {
case INCREMENT:
return Object.assign({}, { counter: state.counter + 1 });
case DECREMENT:
if (state.counter === 0) { return state; }
return Object.assign({}, { counter: state.counter - 1 });
}
return state;
}
AppModule:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { PrincessComponent } from './princess.component';
import { NgReduxModule, NgRedux } from '@angular-redux/store';
import { INITIAL_STATE, IAppState, rootReducer } from './store';
@NgModule({
declarations: [
AppComponent,
PrincessComponent
],
imports: [
BrowserModule,
FormsModule,
NgReduxModule
],
providers: [],
bootstrap: [ AppComponent ]
})
export class AppModule {
constructor(ngRedux: NgRedux<IAppState>) {
ngRedux.configureStore(rootReducer, INITIAL_STATE);
}
}
AppComponent:
import { Component } from '@angular/core';
import { NgRedux, select } from '@angular-redux/store';
import { IAppState } from './store';
import { INCREMENT, DECREMENT } from './actions';
@Component({
selector: 'app-root',
template: `
<div class="container">
<h1>{{ title }}</h1>
<div class="left">
<p>Counter: {{ myCount | async }}</p>
<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
</div>
<div class="right">
<app-princess></app-princess>
</div>
</div>
`
})
export class AppComponent {
title = 'Tour of Technologies';
@select('counter') myCount;
constructor(private ngRedux: NgRedux<IAppState>) { }
increment() {
this.ngRedux.dispatch({ type: INCREMENT });
}
decrement() {
this.ngRedux.dispatch({ type: DECREMENT });
}
}
Let's view the results in Chromium:

TodoListComponent:
import { Component, ViewEncapsulation } from '@angular/core';
import { NgRedux, select } from '@angular-redux/store';
import { IAppState } from './store';
import { ADD_TODO, TOGGLE_TODO, REMOVE_TODO } from './actions';
@Component({
selector: 'app-todo-list',
template: `
<h2>Todo List</h2>
<input type="text" #box>
<ul>
<li *ngFor="let todo of todos | async">
<span [class.completed]="todo.isCompleted" (click)="toggle(todo)">{{ todo.name }}</span>
<button (click)="remove(todo)">X</button>
</li>
</ul>
<button type="button" (click)="add(box.value); box.value='';">Add</button>
`,
styles: [`.completed { text-decoration: line-through; }`],
encapsulation: ViewEncapsulation.Emulated
})
export class TodoListComponent {
@select() todos;
constructor(private ngRedux: NgRedux<IAppState>) {}
toggle(todo) {
this.ngRedux.dispatch({ type: TOGGLE_TODO, id: todo.id });
}
add(todo) {
if (!todo) { return; }
this.ngRedux.dispatch({ type: ADD_TODO, name: todo });
}
remove(todo) {
this.ngRedux.dispatch({ type: REMOVE_TODO, id: todo.id });
}
}
TodoDashboardComponent:
import { Component, ViewEncapsulation } from '@angular/core';
import { NgRedux, select } from '@angular-redux/store';
import { IAppState } from './store';
import { REMOVE_ALL } from './actions';
@Component({
selector: 'app-todo-dashboard',
template: `
<div class="bg-color">
<h2>Dashboard</h2>
<div><b>Last Update:</b> {{ (lastUpdate | async) | date:'mediumTime' }}</div>
<div><b>Total Items:</b> {{ (todos | async).length }}</div>
<button (click)="clearAll()">Clear All</button>
</div>
`,
styles: [`.bg-color { background-color: #e6e6e6; }`],
encapsulation: ViewEncapsulation.Emulated
})
export class TodoDashboardComponent {
@select() todos;
@select() lastUpdate;
constructor(private ngRedux: NgRedux<IAppState>) { }
clearAll() {
this.ngRedux.dispatch({ type: REMOVE_ALL });
}
}
Actions:
export const ADD_TODO = 'ADD_TODO';
export const REMOVE_TODO = 'REMOVE_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const REMOVE_ALL = 'REMOVE_ALL';
Store:
import { ADD_TODO, REMOVE_TODO, TOGGLE_TODO, REMOVE_ALL } from './actions';
export interface IAppState {
todos: any[];
lastUpdate: Date;
}
export const INITIAL_STATE: IAppState = {
todos: [],
lastUpdate: null
};
export function rootReducer(state: IAppState, action): IAppState {
switch (action.type) {
case ADD_TODO:
const newTodo = { id: state.todos.length + 1, name: action.name };
return Object.assign({}, { todos: state.todos.concat(newTodo),
lastUpdate: new Date() });
case REMOVE_TODO:
return Object.assign({}, { todos: state.todos.filter(t => t.id !== action.id),
lastUpdate: new Date()});
case TOGGLE_TODO:
const todo = state.todos.find(t => t.id === action.id);
const index = state.todos.indexOf(todo);
return Object.assign({}, { todos: [ ...state.todos.slice(0, index),
Object.assign(todo, { isCompleted: !todo.isCompleted}),
...state.todos.slice(index + 1)], lastUpdate: new Date()
});
case REMOVE_ALL:
return Object.assign({}, { todos: [], lastUpdate: new Date()});
}
return state;
}
AppModule:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { PrincessComponent } from './princess.component';
import { TodoListComponent } from './todo-list.component';
import { TodoDashboardComponent } from './todo-dashboard.component';
import { NgReduxModule, NgRedux } from '@angular-redux/store';
import { INITIAL_STATE, IAppState, rootReducer } from './store';
@NgModule({
declarations: [
AppComponent,
PrincessComponent,
TodoListComponent,
TodoDashboardComponent
],
imports: [
BrowserModule,
FormsModule,
NgReduxModule
],
providers: [],
bootstrap: [ AppComponent ]
})
export class AppModule {
constructor(ngRedux: NgRedux<IAppState>) {
ngRedux.configureStore(rootReducer, INITIAL_STATE);
}
}
AppComponent:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div class="container">
<h1>{{ title }}</h1>
<div class="left">
<app-todo-list></app-todo-list>
<app-todo-dashboard></app-todo-dashboard>
</div>
<div class="right">
<app-princess></app-princess>
</div>
</div>
`
})
export class AppComponent {
title = 'Tour of Technologies';
}
Let's have a look at the results in our Chromium Web Browser:
