Why Aurelia & Redux is a natural and powerful combination

Accessible, modern and forward-looking programming

Forest Carlisle

Forest Carlisle

Guild Master: Full Stack
For over 10 years, I have worked on or managed teams with remote members. Remote teams present unique challenges, but with today’s technology and intentional interactions it is possible for the remote team to be as connected and productive as a team that all work in the same room.
Forest Carlisle

Latest posts by Forest Carlisle

Another front-end framework, really?

I know. I know. I hear your dev rage swelling, “Stop the endless searching for rainbow unicorns and pick something already.”

I will. I have. That is the point of this article. My goal is to share my reasoning for the pick I made. Hopefully, this will either solidify your choice or give you some new insights if you are still framework-less.

TL;DR

Angular — Too much Angular specific stuff to learn (directives, transclusion, see glossary).
Ember — Too much framework and boilerplate code.
React — Just the view layer — too many decisions to make to piece it all together.
Others — Not mature, small community, unknown core team, unclear vision, etc.

Aurelia is modern, forward-thinking, modular, easy to understand, powerful, fast, and has a committed core team and a passionate community.

What is Aurelia?

Aurelia is not a monolithic framework that forces its will on you. It is a collection of feature-oriented modules with well defined APIs, which when used together, provide a powerful platform for building web applications on open standards. The Aurelia modules, such as metadata, dependency injection, binding, templating, and router, are all separate and can be used in isolation or together. The modules provided by Aurelia provide just about everything you need to build a modern web application without having to piece together individual libraries. One seemingly major omission is the “M” in Model-View-Controller, or more accurately for Aurelia, Model-View-ViewModel. A missing model layer is a good thing, and we will get to that later.

Key Features

Aurelia has comprehensive resources for learning and excellent documentation, so instead of restating all of the features that make Aurelia compelling, I am going to link you to the Aurelia site so you can see for yourself. I will highlight some of the standouts that were significant in my decision.

Aurelia is composed of smaller, focused modules.

Aurelia lets you create custom elements and attributes. It supports dynamic template loading, data binding and high-performance batched rendering.

Speaking of speed, in 3rd-party benchmarks like DB Monster, Aurelia renders faster than any other framework today. Because of its batched rendering and observable object pooling. Aurelia also utilizes less memory and causes less GC churn than other frameworks.

Aurelia supports (optional) conventions to make common tasks effortless. The reduced boilerplate code keeps the frameworks surface area small without stepping into the magic realm.

Aurelia is written in standards-based ES2015+ and TypeScript. Developers are free to use the web programming language they choose. I have been using TypeScript in recent projects and seeing practical benefits (TODO: write an article on practical TypeScript).

By combining ES2015 modules with a simple Dependency Injection container, Aurelia makes it easy to unit test your code. No excuses.

Aurelia is forward-looking and modular. It leverages W3C Web Components specs to facilitate component reuse, sharing, and even usage of 3rd-party Web Components frameworks like Polymer. It supports feature-based development so multiple teams can efficiently work in parallel on the same app.

Aurelia was designed to be extensible and has strict adherence to web standards. This makes it trivial to integrate any 3rd party library or framework, including jQuery, React, Polymer, Bootstrap, MaterializeCSS and many more.

State Management

Aurelia is non-prescriptive when it comes to the model layer and state management. At first, I was confused and annoyed by this. I was expecting “the framework” to tell me how to get data from the server and manage state on the client. As I read more and began to research what others were using with Aurelia, I realized “it depends” was often the crux of the answer.

If you are starting your server-side application fresh, you might go with something GraphQL based. If you have an existing .NET based application built with the Entity Framework, then breeze.js is a natural choice. If you are leveraging Google Firebase for real-time data, then you need to roll your own.

There are two data related things you need to deal with: how to get and send data to your server-side and how to manage state within the app.

Some libraries like Breeze.js do both, and others like Redux are only concerned with state management. Since interacting with the server-side can be vastly different from project to project I decided that focusing on the state management problem within Aurelia apps was more critical to establishing common patterns that can be applied to future projects.

After some experimentation and lots of small refactorings, I have found a few simple patterns that make Aurelia + Redux a natural and powerful combination. Aurelia provides the necessary tools with its Dependency Injection, Templating, and Binding modules that make it trivial to integrate the Redux managed state and reflect state changes in the views.

In the examples below, I will show how I integrate Redux and some of my other favorite things about Aurelia.

Examples

Full source for the below examples can be found here, and you can interact with the running code here.

Custom Element

One of the primary tools you will use to break up and componentize your Aurelia application is custom elements. Custom elements are created from an HTML view and a Javascript view-model pair. Properties (data) and functions (behavior) defined in the view-model are accessible in view. You can also define attributes on the custom element that is bindable and allow for data to be passed into and used by the custom element.

Aurelia’s templating, and data binding modules are very powerful, allowing you to bind HTML attributes, DOM Events and element content.

In the example below, the card custom element is included, and three attributes are used to bind data into the element (i.e., title, icon-class, and count). The count property on the Card custom element is bound to the rockCount property on the App. This is an example of one-way data binding and works perfectly with Redux and the philosophy of one-way data flow. In the update method in App, you can see rockCount is updated whenever the state changes. When the value of rockCount changes Aurelia’s binding system kicks in and propagates that change into the card custom elements bound count property, and the view is updated. Also, notice how easy it is to bind the mouseover and mouseout DOM events to handlers in the view-model.

app.html

<template>
   <require from="card"></require>
   <require from="three-converter"></require>
 
   
       <card class="col" title="Rock" icon-class="fa-hand-rock-o" count.bind="rockCount"></card>
 </template>

card.html

<template>
   <div class="col card text-center border ${borderColor}">
     <div class="card-body" mouseover.delegate="mouseover()" mouseout.delegate="mouseout()">
       <h4 class="card-title">${title} <i class="fa ${iconClass}"></i></h4>
       <p class="card-text display-3">${count}</p>
     </div>
   </div>
 </template>

card.js

import { bindable } from 'aurelia-framework';
 
 export class CardCustomElement {
   @bindable title = "";
   @bindable iconClass = "";
   @bindable count = null;
   
   borderColor = '';
   
   mouseover() {
     this.borderColor = 'border-success';
   }
   
   mouseout() {
     this.borderColor = '';
   }
 }

Value Converter

Often you have data, such as a date, that you want to keep in a generic format but when displaying this data to a user, you want to transform the data into a more human-readable format. This is the purpose of value converters.

In the example below, the ShowThreeValueConverter is replacing the number “3” with the word “three.”

three-converter.js

export class ShowThreeValueConverter {
   toView(value) {
     return (parseInt(value) === 3) ? 'three' : value;
   }
 }

app.html

<template>
   <require from="card"></require>
   <require from="three-converter"></require>
 
   
       <card class="col" title="Rock" icon-class="fa-hand-rock-o" count.bind="rockCount | showThree"></card>
 </template>

Dependency Injection

Aurelia’s dependency injection container allows a developer to break apart their application into small pieces with a single responsibility and then re-assemble the pieces at runtime to be used together. This decoupling is a best practice that allows for more maintainable, reusable, and testable code.

In the example below, the ApplicationStore and GameActions are injected into the App.

app.js

import { inject } from 'aurelia-framework';
import { ApplicationStore } from 'store';
import { GameActions } from 'actions';

@inject(ApplicationStore, GameActions)
export class App {
  
  // the ApplicationStore and GameActions dependencies are injected
  constructor(store, gameActions){
    this.store = store;
    this.gameActions = gameActions;    
  }
}
 

Integrating Redux

As mentioned above, Redux is used for state management. In many Redux examples, you will see the store and action functions defined in and exported from simple modules. To take full advantage of Aurelia and it’s DI container you should hide the details in classes.

In the example below, the ApplicationStore and GameActions are injected into the App. You should inject the ApplicationStore into all components where you need data from the store. In the App constructor, I subscribe to changes in the store and then in the update method I pull out of the state the data that a particular view-model, App in the case, needs for the view.

Typically, I create many action classes related the different features in my application (e.g., GameActions, NotificationActions, AuthActions, etc.). Action classes will often need to make asynchronous HTTP requests to fetch data from a remote data source. This data fetching behavior should be in its own class, and that class should be injected into the action classes that need this functionality.

app.js

import { inject } from 'aurelia-framework';
import { ApplicationStore } from 'store';
import { GameActions } from 'actions';

@inject(ApplicationStore, GameActions)
export class App {
  rockCount = paperCount = scissorsCount = 0;
  
  // the ApplicationStore and GameActions dependencies are injected
  constructor(store, gameActions){
    this.store = store;
    this.gameActions = gameActions;
    
    // ...
    
    // subscribe to the store's state changes
    this.unsubscribe = this.store.subscribe(() => {

this.update();
    });
    this.update();
  }
  
  // get the game data from the state when it changes
  // and update our view model data
  update() {
    let state = this.store.getState();
    
    this.rockCount = state.game.rock;
    this.paperCount = state.game.paper;
    this.scissorsCount = state.game.scissors;
  }
  
  // play button click handler
  // dispatches the game make choice action
  play() {
    this.store.dispatch(this.gameActions.makeChoice());
  }
}

store.js

import reducer from 'reducer';
const { createStore } = Redux; // import {createStore} from 'redux';

// just a simple wrapper around the redux store
export class ApplicationStore {
  constructor() {
    this.store = this._configureStore();
  }

  // ...

  _configureStore() {
    return createStore(reducer, {}, window.devToolsExtension ? window.devToolsExtension() : undefined);
  }
}

actions.js

// actions related to game play

export class GameActions {
  constructor() { }
  
  makeChoice() {
    var result = this.getRandomInt(1,3);
    
    switch (result) {
      case 1: // rock
        return { type: 'GAME_ROCK' };
      case 2: // paper
        return { type: 'GAME_PAPER' };
      case 3: // scissors
        return { type: 'GAME_SCISSORS' };
    }
  }
  
  getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }
}

Next Steps

Hopefully, you have gotten a small glimpse of why I find Aurelia compelling — the code is simple, it encourages good practices, it is very powerful and other than state management has everything you need to build real applications. Below are some resources I have found useful for learning and digging deeper into Aurelia.

Official Docs and Resources

Aurelia Guides

Aurelia Cheat Sheet

Aurelia Blog – watch for examples, major updates, and regular release notes.

Aurelia Discourse and Aurelia Gitter – for community help and support.

For new Aurelia projects, I suggest using the Aurelia CLI with Webpack.

Example building a typeahead control with some advanced Aurelia usage.

 

Share This Article


Forest Carlisle

Forest Carlisle

Guild Master: Full Stack
For over 10 years, I have worked on or managed teams with remote members. Remote teams present unique challenges, but with today’s technology and intentional interactions it is possible for the remote team to be as connected and productive as a team that all work in the same room.
Forest Carlisle

Latest posts by Forest Carlisle

No Comments

Post A Comment