Aurelia

Creating Reactive, Loosely Coupled Apps with Aurelia and Flux - Episode 1

Introduction

Aurelia

Aurelia


Creating Reactive, Loosely Coupled Apps with Aurelia and Flux - Episode 1

Posted by Aurelia on .
Featured

Creating Reactive, Loosely Coupled Apps with Aurelia and Flux - Episode 1

Posted by Aurelia on .

This week we're excited to feature another fantastic member of the Aurelia community: Tomasz Frydrychewicz. Thomas is a full stack developer in love with JavaScript and is always looking for an opportunity to spread that enthusiasm around. He's a big fan of reactive, event driven architectures and asynchronous programing. During the last five years he introduced Angular to different societies but is recently infatuated with Aurelia and its approach to modern JavaScript frameworks. Thomas is the author of the aurelia-flux plugin, which you can hear about in his own words below.


Why should we even bother with reactive programming? Well, we all do care about writing testable, loosely coupled code, but are our dependencies really loose? Imagine that you have a working piece of code implementing particular business logic, and you would like to connect a new feature into the flow. If your code isn't reactive yet, you would have to add more "if-ology" to it, causing you to have to change the existing code base - that is not what I believe loosely coupled dependencies look like. How can the reactive, unidirectional Flux pattern help?

The Flux approach is based on actions, which are the business information carriers, a central dispatcher responsible for controlling action flows and stores, which consume those actions and expose data from queries to views. If it reminds you of the CQRS pattern, you got it.

flux flow diagram Image used from http://facebook.github.io/flux/

Aurelia-Flux is a small but useful library that brings the Flux dispatcher into Aurelia. As Aurelia's goal is to be as close to the programming language as possible, aurelia-flux doesn't require any special sort of inheritance chain, any configuration or conventions. It's ready to work out of the box and can be applied to any existing Aurelia application.

Installing

In order to start using aurelia-flux, just install it with jspm...

jspm install aurelia-flux="github:tfrydrychewicz/aurelia-flux"  

...load the plugin...

export function configure(aurelia) {  
  aurelia.use
    .standardConfiguration()
    .developmentLogging()
    .plugin('aurelia-flux'); //Add this line to load the plugin

  aurelia.start().then(a => a.setRoot()); 
}

...and you're ready to go.

Basic Usage

Using aurelia-flux is as easy as falling off a log. You don't have to create any special types, just start using it with your ES6/7 classes. Create your store and decorate any method with the @handle decorator to make it start consuming the given action.

import {inject} from 'aurelia-framework';  
import {Dispatcher, handle} from 'aurelia-flux';

@inject(Dispatcher)
export class MessagesStore {  
    messages = [];

    constructor(dispatcher) {
        this.dispatcher = dispatcher;
    }

    @handle('message.send')
    addMessage(action, message) {
        this.messages.push(message);
        this.dispatcher.dispatch('message.sent', message);
    }

    getMessages() {
        return this.messages;
    }
}

Then use your store in the view model, along with the dispatcher's dispatch method to make the dispatcher start processing your action and deliver it to all the handlers.

import {inject} from 'aurelia-framework';  
import {Dispatcher, handle} from 'aurelia-flux';  
import {MessagesStore} from 'messages';

@inject(Dispatcher, MessagesStore)
export class Welcome {  
    constructor(dispatcher, store) {
        this.dispatcher = dispatcher;
        this.store = store;
    }

    submit() {
        this.dispatcher.dispatch('message.send', this.message);
    }

    @handle('message.sent')
    notifyWhenMessageSent(action, message) {
        notify(`Message sent: ${message}`);
    }       
}

Create a standard Aurelia view and feed it with the store's data...

<div class="row">  
    <form role="form" submit.delegate="submit()">
        <div class="form-group">
            <label for="msg">Message</label>
            <input type="text" value.bind="message" class="form-control" id="msg">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
    </form>
</div>  
<div class="row">  
    <ul>
        <li repeat.for="message of store.getMessages()">${message}</li>
    </ul>
</div>  

... and you have your application working in a reactive manner with aurelia-flux.

One-Timers

The defined message.send handler will be processing the message.send messages for the whole object life-cycle. If you want to control when it's being released, use the dispatcher's handle method. When invoked, it returns a release method, which will remove the callback from the dispatcher's registry.

oneTimeHandle() {  
    var release = this.dispatcher.handle('message.sent', (action, message) => {
        console.log(message);
        release();
    });
}

Promises

Aurelia-Flux is based on BlueBird promises A+, therefore you can use it with any JavaScript promises. If your callback returns a promise, the flux dispatcher will not finish its current dispatching until the promise is resolved or rejected. If you don't want the dispatcher to wait for your asynchronous call to finish, just don't return the promise.

@handle('message.send')
handleAsync(action, message) {  
    return Promise((resolve, reject) => {
        this.http.post('http://your.service.url/messages', message)
            .then(() => {
                this.dispatcher.dispatch('message.sent', message);
                resolve();
            })
            .catch(() => {
                this.dispatcher.dispatch('message.errorWhenSending', message);
                reject();
            }); 
    }); 
}

WaitFor

As the order of handlers invoked when dispatching an action depends on the particular sequence of files you're loading into your app, you cannot rely on it. Luckily, aurelia-flux comes with waitFor which facilitates and alleviates building sequential action processing. If you want the whole callback function to wait for another store to finish processing, use the @waitFor decorator.

import {handle, waitFor} from 'aurelia-flux';  
import {MessagesStore} from 'messages';

...

@handle('message.send')
@waitFor(MessagesStore)
logMessage(action, message) {  
    ...
}

If only a part of a handler needs to wait, use the dispatcher's waitFor method.

import {handle, waitFor} from 'aurelia-flux';  
import {MessagesStore} from 'messages';

...

@handle('message.send')
logMessage(action, message) {  
    var prepared = prepare(message);
    this.dispatcher.waitFor(MessageStore, () => {
        log(prepared);
    });
}

Summary

Hopefully this short introduction to creating reactive applications with aurelia-flux gave you a good starting point for your reactive adventure. Building applications in such a manner helps to maintain the loose coupling of dependencies, keeps code clean and avoids the excessive use of unnecessary branching.

In the next episode I will walk you through en example, reactive application created with aurelia-flux.

View Comments...