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:

results matching ""

    No results matching ""