Angular.js

Angular 2 Application Architecture – Building Redux-like apps using RxJs

In this post, we are going to walk through how Angular 2 applications can be built in a Functional Reactive style using the RxJs library that is part of Angular 2. This post is based on Managing State in Angular 2 Applications by Victor Savkin (@victorsavkin), check it out for the source of these ideas. Also there is this sample repository with an example of how an application can be built using this style. Let’s then go over the following topics:

  • The difficulty of handling state in single page applications
  • When is a Flux-like architecture needed?
  • Building a Flux-like Angular 2 Application using RxJs
  • Application Actions
  • How to build an Action Dispatcher in RxJs
  • Building an Application State Observable
  • Consuming Observables using the async pipe
  • Where to use RxJs: Smart vs Pure Components
  • Comparison with Redux
  • Conclusions

This post is better read after going through Functional Reactive Programming for Angular 2 Developers – RxJs and Observables, where several of the RxJs operators used in this post are presented.

The difficulty of handling state in single page applications

Probably not all single page applications have a large state management problem. Imagine a simple CRUD app used to administer reference data for security privileges.

For that type of single page application probably using Angular 2 Forms and/or NgModel if you so prefer is an approach that will yield good results with low complexity involved.

But there are use cases where this approach is known to fall short. The most common example is the famous unread messages counter issue in Facebook, that lead to the creation of the Flux architecture (see here the original talk).

When is a Flux-like architecture needed ?

As one of the core members of React (Pete Hunt – @floydophone) says in the React How-To, you probably don’t need Flux! When you need it, you will know when you need it. If you have a use case like the following, you probably need Flux:

  • multiple parts of your app display differently the same data. For example an outlook app displays a message on a list and updates folder counters of unread messages
  • some data can be simultaneously edited by both the user using the UI or events coming from the backend via server push
  • you have a need for undo/redo of at least part of the application state

These use cases are actually not that infrequent, depending on the type of app.

An architecture for complex UIs

Think of the Netflix UI, where a movie potentially appears in multiple lists. If you add a movie to your favorites and scroll down and see that movie again in another list, you want to see it marked as a favorite already. Again, the same data shows up in multiple parts of the UI and an action needs to affect the whole UI consistently – this is a typical use case for Flux.

Is Flux/Redux the only solution for use cases like this?

There are other options too besides the single atom of state option presented in the rest of this post.You can for example like building the application around the notion of observable data services. Thats probably just another version of Flux.

But let’s assume you stumbled upon one of these cases in a part of your app: How can we build a Redux-like single atom of state application in Angular 2?

Building a Flux Angular 2 app using RxJs

As in other Flux approaches, it all starts with the UI Actions. The user interacts with the page and as a result multiple parts of the app want to respond to that interaction.

Application Actions

An action is a message that defines what happened in the UI: A Todo was added, removed, toggled. To make this type safe, lets create a class for each action:

export class AddTodoAction {
    constructor(public newTodo: Todo) {

    }
}

export class ToggleTodoAction {
    constructor(public todo: Todo) {

    }
}

And then lets define a Typescript union type that is a union of all the action types defined:

export type Action = LoadTodosAction | AddTodoAction | ToggleTodoAction | DeleteTodoAction | StartBackendAction | EndBackendAction;

As we can see, the Actions are just POJOs that transport the data necessary for the different parts of the application to adapt themselves.

But how can an Action be dispatched to multiple parts of the application that need it?

Using the Action Dispatcher

An action is dispatched via the action dispatcher. This gets injected in any place of the application that needs to dispatch actions, typically Smart or Controller-like components like the TodoList in the sample application:

export class TodoList {
    constructor(@Inject(dispatcher) private dispatcher: Observer<Action>) {
      ...
    }
}

You might be wondering what the dispatcher name inside the @Inject annotation is, this is just a token name for identifying a specific injectable, more on this later. The important thing to to know now is that the dispatcher can be used to dispatch any action to the rest of the application:

onToggleTodo(todo: Todo) {
    this.dispatcher.next(new ToggleTodoAction(todo));
    ...
}

As we will see later, the dispatcher was built in a few lines of RxJs. But before seeing how its built internally, let’s see how other parts of the application can react to an action.

Defining the application state

Let’s start by defining what the application state looks like:

export interface ApplicationState {
    todos: List,
    uiState: UiState
}

As we can see, the application state consists of a list of todo items which is the data of the application, plus an instance of UiState. Let’s take a look at UiState:

export class UiState {
    constructor(public actionOngoing: boolean, public message:string) {
    }
}

UiState contains any state in the UI other than the data, such as for example what is the message currently being displayed to the user, and a flag indicating if some action is ongoing in the backend.

Introducing the Application State Observable

Now that we know what the state of the application looks like, we need to imagine a stream which consists of the different states that the application has over time: the application state observable.

As new actions are triggered, this stream will emit new values that reflect the result of those actions: todos get added, toggled, deleted, etc. What we are looking into is this:

let applicationStateObs: Observable<ApplicationState>;

We will see later how we can create such an observable, it will only take a couple of lines of RxJs. For now, let’s assume that the application state observable already exists, and that it can be injected anywhere in the application:

export class App {
    constructor(@Inject(state) private state: Observable<ApplicationState>) {
        ...
    }
}

This means that any part of the application that wants to react to new state can have an observable injected and subscribe to it. That part of the application does not know what action triggered the arrival of the new state: it just knows that new state has arrived and that the view should be updated to reflect it.

How to use the application state observable

We can subscribe to the application state observable like to any other observable. Let’s say that we want to take the list of todos and pass it to the template. We could subscribe to the observable and populate a todos list member variable:

state.subscribe(newState => this.todos = newState.todos );

This would be one to do it, but Angular 2 foresees a better way.

Consuming observables using the async pipe

Another way to consume observables is to use the Angular 2 async pipe. This pipe will subscribe to the observable and return its last result:

<ul id="todo-list">
    <li *ngFor="#todo of todos| async">
    ...
    </li>
</ul>

Here we can see that the list of todos comes from a todos observable. This observable is defined via a getter method in a controller class and is derived from the application state observable using the map operator:

get todos() {
        return this.state.map((state: ApplicationState) => state.todos);
    }

As we can see its simple to build a Flux app once the dispatcher and the application state observable are available:

  • inject the dispatcher anywhere an action needs to be triggered
  • inject the application state anywhere in the application that needs to react to a new application state

Let’s now see how these two constructs can be built using RxJs.

Building an Action Dispatcher

The dispatcher is really just a traditional event bus: we want to use it to trigger events, and want some part of the application to be able to subscribe to the actions that it emits.

To implement this, the simplest way is to use an RxJs Subject, that implements both the Observable and the Observer interfaces. This means we can not only subscribe to a Subject, but use it to emit values as well.

Making the dispatcher injectable

The dispatcher to be injected anywhere on the application needs an injection name. Let’s start by creating such name using an OpaqueToken:

export const dispatcher = new OpaqueToken("dispatcher");

This token can then be used to register a Subject in the Angular 2 dependency injection system. Lets add this line to the boostrap statement of the application:

bootstrap(App, [
    ...
    provide(dispatcher, {useValue: new Subject()}),
    ]);

This means that whenever the dependency injection system gets asked for something named dispatcher, this Subject will get injected.

Avoiding event soup while using the dispatcher

Although the dispatcher is a Subject, its better to mark its type upon injection as an Observer only:

constructor(@Inject(dispatcher) private dispatcher: Observer) {
    ...
}

This is because we really only want the dispatcher to be used in application code to dispatch events, such as for example:

this.dispatcher.next(new DeleteTodoAction(todo));

We want to avoid most application code to accidentally subscribe directly to the dispatcher, instead of subscribing to the application state.

There might be valid use cases to subscribe directly to the dispatcher, but most of the time this is likely not intended. With the dispatcher defined, let’s see how can we define the application state observable.

Building an application state observable using RxJs

First let’s define an initial state for the application, again using an injection name of initialState:

provide(initialState, {useValue: {todos: List([]), uiState: initialUiState}}),

This means that whenever the name initialState is requested for injection, the object defined above is passed in: it contains an empty list of todos and some initial non-data UI state.

Defining the application state

The application state observable can be built as follows:

function applicationStateFactory(initialState, actions: Observable<Action>): Observable<ApplicationState> {
    ...
}

The application state is a a function of both the initial state and the stream of actions that occur over time:

  • the first value of the state observable is the initial state itself
  • then the first action occurs and the initial state is transformed accordingly and the state observable emits the new state
  • the next action is triggered, a new state is emitted

Calculating the new application state

Each new state is the result of applying the new action to the previous state, which looks a lot like a functional reduce operation. The first step to calculate the new application state is then to define a series of reducing functions, Redux style. Here we have the reducer function for all todo-related actions:

function calculateTodos(state: List, action) {
    if (!state) {
        return List([]);
    }
    if (action instanceof  LoadTodosAction) {
        return List(action.todos);
    }
    else if (action instanceof AddTodoAction) {
        return state.push(action.newTodo);
    }
    else if (action instanceof ToggleTodoAction) {
        return toggleTodo(state, action);
    }
    else if (action instanceof DeleteTodoAction) {
        let index = state.findIndex((todo) => todo.id === action.todo.id);
        return state.delete(index);
    }
    else {
        return state;
    }
}

This is a typical reducing function: taking a state and an action, calculate the next state of the todo list. A similar reducing function
calculateUiState can be defined for the UiState part of the application state (see here).

Using reducers to produce a new application state stream

Now that we have the reducer functions, we need to define a new observable stream that takes the actions stream and the initial state, and produces a new state.

In a previous post, we went over some commonly used RxJs operators. Its time to use the first of those operators, the scan operator. This operator takes a stream and creates a new stream that provides the output of a reduce function over time.

let appStateObservable = actions.scan( (state: ApplicationState, action) => {

   let newState: ApplicationState = {
       todos: calculateTodos(state.todos, action),
       uiState: calculateUiState(state.uiState, action)
   };

   return newState;

} , initialState);

What we are doing here is taking the current state of the application, starting with the initial state, and then continuously calculate the new state as a result of the previous state and the action itself.

The output of scan is also an observable whose values are the several states of the application over time.

And that’s it! As the Redux docs themselves mention, an alternative to Redux is a few lines of RxJs! But there are still a couple of loose ends we will look into.

Avoiding reducer functions from being called multiple times for one action

While debugging an application built like this, you will notice that if you add multiple application state consumers (like multiple async pipes), the reducer functions will be hit multiple times, one for each subscriber.

This is because each observable by default spawns a separate processing chain, see this previous post for further details.

Although there is nothing fundamentally wrong with that, for the sake of simplicity of debugging you probably want to ensure that the reducer functions are only triggered once per action dispatched, like in Redux.

For this we introduce the second RxJs operator we will use, the share operator (see here for more on that):

return appStateObservable.share();

There is another loose end, which is how to use the application state observable in an application startup scenario.

Ensuring that the application state observable can be consumed at application startup

You might want to inject the application state observable and use it in places in your application where your whole app is not completely setup yet.

For example, at application startup time, like here. This will not work the way you expect, because its possible that not all subscribers are wired at the moment the first application state value returns.

The solution for this is to make the application state observable a stream that when subscribed always returns the last value, even if it was emitted in the past before the subscription took place.

For this, we need to wrap the plain state observable into a
BehaviourSubject:

function wrapIntoBehaviorSubject(init, obs) {
    const res = new BehaviorSubject(init);
    obs.subscribe(s => res.next(s));
    return res;
}

return wrapIntoBehaviorSubject(initialState, appStateObservable);

This will ensure that subscribers will always receive at least the initial state value.

All Together Now

This is a working example of how to build an application state observable (see here as well):

export function applicationStateFactory(initialState: ApplicationState, actions: Observable): Observable {

    let appStateObservable = 
        actions.scan( (state: ApplicationState, action) => {

        console.log("Processing action " + action.getName());

        let newState: ApplicationState = {
          todos: calculateTodos(state.todos, action),
            uiState: calculateUiState(state.uiState, action)
        };

        console.log({todos: newState.todos.toJS(),uiState: newState.uiState});

        return newState;

    } , initialState).share();

    return wrapIntoBehaviorSubject(initialState, appStateObservable);
}

We can use this factory function to create an injectable application state observable in the following way:

bootstrap(App, [
...
provide(state, {useFactory: applicationStateFactory, deps: [new Inject(initialState), new Inject(dispatcher)]})
]);

And this wraps up how a application state observable can be built. Let’s remember that once this initial plumbing is in place, its really a matter of injecting the dispatcher and application state where we need it, and mostly writing new reducer functions.

Where to use State and Dispatcher – Smart vs Pure Components

In general, the dispatcher and state observable should only be injected in smart components. Those components don’t need to have any state variables, as they can consume the state observable directly using the
async pipe.

Its not that the application has no state, but that the application code has no state. The state is managed by the RxJs library and not at the level of the application.

How should pure components use dispatcher and state

Pure components can receive observables as input streams, but they should not have the dispatcher or state observable injected into them, as this would bind them to this particular application, making them not reusable.

If a pure component wants to dispatch an action, it instead issues an event via EventEmitter, and its the smart component that will subscribe to the event emitter it and in response dispatch an action.

Comparison of building Angular 2 apps with Redux

Let’s keep in mind that we will already need to know RxJs to use Angular 2, because Observables are part of the Angular 2 API for things like Forms or Http.

And as per the docs of Redux itself, the same concept as Redux can be implemented in RxJs using the scan operator and a couple of operators more, as we have just saw. Also RxJs is already shipped with Angular 2.

So it would seem to make sense when coming across use cases that require Flux to implement them in RxJs instead of Redux, if by anything else by a matter of using a library we already have to know how in any case. Using a Redux implementation made in RxJs like ngrx is a good way to go too.

In the end the solid concepts around Redux are more important than the library itself.

Alternatives

Another possible alternative to single atom of state applications is to build an application around the notion of observable data services.

Conclusions

Its still early for the Angular 2 community to know what will a typical Angular 2 application look like, and how state will be handled.

With all these options, its easy to loose sight of something important. There are for sure ways to do Flux-like apps in Angular 2, but the main question is do we really need a Flux-like architecture for our entire application? Or for only a certain screen or more advanced use case?

A good way to evaluate if Flux is beneficial is the React How-To guide, this question has some info too, and the original Flux talk.

Aleksey Novik

Software developer, Likes to learn new technologies, hang out on stackoverflow and blog on tips and tricks on Java/Javascript polyglot enterprise development.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button