Migrating from Durandal to Aurelia

Christian Kotzbauer
3 min readFeb 21, 2020

--

Originally published on June 15, 2016

If you have an existing Durandal application you maybe want to migrate this app to Aurelia. Typically you used Knockout for data-binding. Because Aurelia brings its own data-binding language the usage of Knockout is not necessary anymore. But probably especially this part of the migration requires the greatest effort, because you have to modify much of your code or partially rewrite it. So let’s split this effort as much as possible:

Here’s a way how you can use Aurelia side by side with the Knockout technology and then migrate each view and view-model after another. You can use your old Knockout views and their backed JavaScript code with its observables and subscriptions, as if you were still using Durandal.

First Steps

First, install the Aurelia plugin

jspm install aurelia-knockout

and register it during the startup of your app:

export function configure(aurelia) => {
aurelia.use
.standardConfiguration()
.developmentLogging()
.plugin("aurelia-knockout");
aurelia.start().then(() => aurelia.setRoot());
}

The next step will be, to add the knockout custom attribute to each view which uses Knockout syntax:

<template>
<div knockout>
<button data-bind="click: toggle">Change Visibility</button>
<div data-bind="if: isVisible">
<span data-bind="text: firstName"></span>
<br/>
<span data-bind="text: lastName"></span>
</div>
</div>
</template>

Note: The element with the custom attribute has to wrap all code with Knockout syntax.

This attribute binds the HTML to the current BindingContext of Aurelia.

And that’s it! With these small modifications you can use all Knockout bindings which are available.

Using Compositions

Durandal also provided a binding to compose views. Because this binding was implemented from Durandal and not from Knockout you can not use this feature natively. But the aurelia-knockout plugin supports the composition with all possible variants listed in the official Durandal docs:

<div data-bind="compose: 'path/to/view.html'"></div>
<div data-bind="compose: 'path/to/module'"></div>
<div data-bind="compose: { view: 'path/to/view.html' }"></div>
<div data-bind="compose: { model: 'path/to/module' }"></div>
<div data-bind="compose: { model: moduleInstance }"></div>
<div data-bind="compose: { view: 'path/to/view.html' model: 'path/to/module' }"></div>
<div data-bind="compose: { view: 'path/to/view.html' model: moduleInstance }"></div>
<div data-bind="compose: moduleInstance"></div>
<div data-bind="compose: moduleConstructorFunction"></div>

Set @bindable properties

To enhance the flexibility of your migration process, this plugin also supports the ability to set @bindable variables in rewritten subordinated Aurelia views through the activationData. This offers the possibility to rewrite single views in lower hierarchies without the need to do the same with their parent views.

Parent Knockout based view

Supposed that there is a parent view using Knockout technology which composes a child view like this:

<template>
<div data-bind="compose: { model: 'path/to/submodule', activationData: data }"></div>
</template>

The appropriate data object would looks like this:

{
price: ko.observable(5),
productName: "Apples"
}

Child Aurelia based view

The child view and view-model uses Aurelia:

<template>
Product: <span>${productName}</span>
<br/>
Price: <span>${price}</span>
</template>
import { bindable } from "aurelia-framework";
import { inject } from 'aurelia-dependency-injection';
import { KnockoutBindable } from "aurelia-knockout";
@inject(KnockoutBindable)
export class ProductView {
@bindable
price;
productName;
knockoutBindable; constructor(knockoutBindable) {
this.knockoutBindable = knockoutBindable;
}
activate(activationData) {
this.knockoutBindable.applyBindableValues(
activationData, this);
}
}

This will set the value from activationData.price to this.price. this.productName however, is not updated because it has no @bindable decorator and the variable from activationData is no Knockout observable. To process non Knockout observables anyway you have to pass false as third parameter to the applyBindableValues function. If the outer value changed (and is an observable) the corresponding inner variable is updated too.

Subscriptions for Knockout observables which are created from this plugin internally will be disposed automatically if the child view is unbound.

As you can see, the migration to a new framework can be a big task depending on your application. But that does not mean that you have to implement it all at once.

--

--

Christian Kotzbauer

Web Developer with TypeScript and Aurelia. Interested in Node.js, Security, Docker and Kubernetes