Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Redux From Scratch With Angular and RxJS

Episode 118 written by Jeff Delaney
full courses for pro members

Health Check: This lesson was last reviewed on and tested with these packages:

  • Angular v6
  • RxJS v6.2

Find an issue? Let's fix it

Source code for Redux From Scratch With Angular and RxJS on Github

Building your own state management solution in Angular from scratch is surprisingly easy - at a fundamental level anyway. In this lesson, you will learn the core concepts behind redux, as well as some very useful RxJS techniques like custom operators and multicasting. My goal is to help you understand how to setup state management from the ground up, but also to understand the underlying principles if you use a library like NgRx or NGXS.

A demo of the redux state management system from scratch in Angular using Redux Dev Tools

One major benefit of building your own state management tool is that you can create your own abstractions to reduce boilerplate code. Redux is notorious for its large code footprint, but you can use convention over configuration to get all the benefits with far less explicit configuration and duplication.

Concept 0 - What is Redux?

Redux is a pattern/library from the React world that has inspired popular Angular tools like NgRx and NGXS. The purpose of redux is to make application data more predictable by creating a one-way data flow.

A visual depiction of the redux pattern in web development

The premise is simple - Your application data is a single immutable object that can only be changed by dispatching actions. Let’s get started by generating a service in Angular to serve as our global state management tool.

ng new reduxApp
npm install lodash
ng generate service store

I am also installing Lodash because it provides some helpers that will come in handy and provide better performance than a native implementation.

Our store service only has two properties, both of which are reactive streams of data - actions and state.

import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { scan } from 'rxjs/operators';
import { omit } from 'lodash';


@Injectable({
providedIn: 'root'
})
export class Store {
state: Observable<any>;
actions: Subject<Action> = new Subject();

}

Concept 1 - Action Stream

Let’s define an action class to standardize the way changes occur.

export class Action {
constructor(public type: string, public payload?: any) {}
}

Actions signal a mutation to the state container and provide an optional data payload. It is the actions stream that determines the shape of the state.

Concept 2 - Reducing the State

The state listens to the action stream, then changes the current state object to the next state object. In order to facilitate this task, I am extracting this logic to a custom RxJS operator named reducer.

export class Store {
constructor() {
this.state = this.actions.pipe(
reducer()
);
}

The reducer itself is just a switch statement that builds the next state object based on the action that was dispatched to it. Think of this as your event handler - it’s a pure function so I’ve defined as a regular function outside of the Angular service class. I’ve included some code for your basic CRUD operations on an object.

export const reducer = () =>
scan<any>((state, action) => {
let next;
switch (action.type) {
case 'SET':
next = action.payload;
break;
case 'UPDATE':
next = { ...state, ...action.payload };
break;
case 'DELETE':
next = omit(state, action.payload);
break;
default:
next = state;
break;
}

return next;
}, {});

Concept 3 - Dispatching Actions

Dispatching an action is easy because we’re using an RxJS Subject. We can push the next value into the stream by calling next.

export class Store {

dispatch(action: Action) {
this.actions.next(action);
}

}

Concept 4 - Selecting State

Selecting data is a little more complex than it seems on the surface. First of all, we need to observe a deep path from the source state object. Also, we only want this observable to emit data when the it’s scope of interest changes. To handle that magic, we combine the Rx distinctUntilChanged operator with the Lodash isEqual function to perform a deep object comparison.

import { map, distinctUntilChanged, shareReplay } from 'rxjs/operators';
import { get, isEqual } from 'lodash';

export class Store {

select(path: string) {
return this.state.pipe(slice(path));
}

}



export const slice = path =>
pipe(
map(state => get(state, path, null)),
distinctUntilChanged(isEqual)
);

Also, we want new subscribers to receive the last value. We can tell our source subject to save the last emitted value with shareReplay and pass 1 because we only need the most recent value to be cached.

export class Store {
constructor() {
this.state = this.actions.pipe(
reducer(),
shareReplay(1)
);
}

Concept 5 - Pure Components

We have now reached the glorious promised land of pure components, where we only (1) select state or (2) dispatch actions. Components like this are much easier to debug and test.

import { Component } from '@angular/core';
import { Store, Action } from './store.service';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
spanish;
constructor(private store: Store) {
this.spanish = store.select('spanish.hola');
}

set() {
this.store.dispatch(new Action('SET', { hello: 'world' }));
}

update() {
this.store.dispatch(new Action('UPDATE', { spanish: { hola: 'mundo' } }));
}

delete() {
this.store.dispatch(new Action('DELETE', 'spanish'));
}
}

And our HTML can unwrap the observables with the async pipe:

<h3>Global State</h3>
{{ store.state | async | json }}

<h3>Selected State</h3>

{{ spanish | async | json }}

<hr>
<button (click)="set()">Set</button>
<button (click)="update()">Update</button>
<button (click)="delete()">Remove</button>

Bonus Concept - Redux Dev Tools

One of the main reasons people like Redux this awesome browser extension called Redux Dev Tools for visualizing state. You probably think it’s hard to wire this thing up, right? Nope, it takes only a few lines of code because we already have the necessary data in our reducer function.

const win = window as any;
win.devTools = win.__REDUX_DEVTOOLS_EXTENSION__.connect();

// ...

export const reducer = () =>
scan((state, action) => {

// ...omitted
win.devTools.send(action.type, next);
return next;
}, {});

Don’t forget to install the Redux Dev Tools extension from the Chrome web store.

The End

Congrats, you just implemented the hottest state management pattern in web development all by yourself. Now you can build on this to create a lean solution that works exactly the way you want it.