So you’ve decided it’s time to join the future and port your existing Angular application to Aurelia. Your first port of call was probably the documentation and chances are you probably got confused, because Aurelia lacks the concept of a “directive” – don’t be discouraged my friend, there is light at the end of the tunnel.
The concept of directives do exist in Aurelia, however they go by the names: Custom Attribute and Custom Element. In Angular 1.x, a directive can both be a custom attribute and a custom element. It is a confusing concept that fortunately has been removed in Angular 2.
If you’re looking to jump the fence to greener pastures into Aurelia land, then this article will show you how to port over Angular directives to Aurelia.
Part I: Custom Elements
In AngularJS 1.x a custom element is created using what is called a directive. In Aurelia a directive is either a custom element or custom attribute. The terminology is different, Aurelia’s is more concise and explanatory in comparison to Angular’s use of the word directive.
In this section we are going to create a custom HTML element which is used using the <person>
HTML element. We will also have the ability to pass through an object of data which the custom element will use to populate itself.
Angular
As we discussed earlier, custom elements in Angular 1.x are directives with the restrict property set to ‘E’ — lets proceed.
Create the directive
Firstly, lets define our custom element in a file we will aptly call person-directive.js
– this will be responsible for setting up what type of directive we are creating and what data it can accept.
angular.module('myApp', [])
.directive('person', function() {
return {
restrict: 'E',
scope: {
person: '=data'
},
templateUrl: 'person-directive.html'
}
});
Create the directive view template
Although Angular directives allow us to create inline templates, to keep in the spirit of doing things the way we can do them in Aurelia, we will be creating a separate view template called person-directive.html
<div class="person">
<div>Name: {{person.name}}, Location: {{person.location}}</div>
</div>
Using our directive
As you can see we have our custom element created. We can reference it using <person>
and it if the passed in data matches what we expect in the view, we will see their name and location.
Create a controller (aka ViewModel)
This controller handles passing through data to a specific part of our application. You might access this controller through a route called “/people” if this were a real application. In this controller we define some mock data, an array of people objects.
angular.module('myApp', [])
.controller('PersonController', function($scope) {
$scope.people = [
{name: 'Dwayne', location: 'Australia'},
{name: 'Rob', location: 'USA'},
{name: 'Max', location: 'France'}
];
});
Create a controller view (aka View template)
This might not reflect how you would render the above controller in the real world, but for our purposes it will work just fine. Presumably here we would be including Angular itself from a CDN, our directive and controllers.
<html>
<head>
<title>PeopleApp</title>
</head>
<body>
<div ng-app="myApp" ng-controller="PersonController">
<ul>
<li ng-repeat="person in people">
<person data="person"></person>
</li>
</ul>
</div>
</body>
</html>
As you can see, Angular is a horrible mess. Some concepts kind of make sense, especially if you’re drunk or high. But honestly, concepts such as $scope
and restrict
are constant sources of confusion and frustration for newcomers.
Aurelia
Now we are going to create the same custom element within Aurelia. In Aurelia custom elements are referred to as custom elements, so no confusing double-meaning directive terminology here.
One of the first things you might notice is we have considerably less code in comparison to the above example.
Create the custom element
The below example is a little more verbose. We can also specify an element as a custom element or attribute by naming the class PersonCustomElement
and remove the @customElement
decorator if we wanted to shave off an extra line of code.
We are assuming this file is called person-element.js
(this is important as we will import this later using this name).
import {customElement, bindable, bindingMode} from 'aurelia-framework';
@customElement('person')
@bindable({ name: 'person', attribute: 'data', defaultBindingMode: bindingMode.twoWay})
export class Person {
}
Create the custom element view template
You might have noticed that we didn’t specify a view template name above. Because Aurelia favours a convention based approach, it assumes a ViewModel has a paired View of the name filename, just with a .html
extension instead.
Using the above mentioned convention, we call this file person-element.html
and leave it to Aurelia to pair them up.
<template>
<div class="person">
<div>Name: ${person.name}, Location: ${person.location}</div>
</div>
</template>
Using our custom element
We use our custom element the same way we used it above in our Angular example. We still reference our element using <person>
inside of our View. This is the only comparable feature between Angular and Aurelia in how something works.
Create a ViewModel (aka controller)
In Aurelia the concept of a controller does not exist. Everything utilises ViewModel’s which are presumed to be paired with a View template (using default conventions). In our case we’ll just call it my-view.js
export class MyView {
people = [
{name: 'Dwayne', location: 'Australia'},
{name: 'Rob', location: 'USA'},
{name: 'Max', location: 'France'}
];
}
Create a View (aka controller template)
<template>
<require from="./person-element"></require>
<ul>
<li repeat.for="person of people">
<person data.bind="person"></person>
</li>
</ul>
</template>
Conclusion
The difference is night and day between the two. As you can see porting over Angular directives to Aurelia is quite easy and considerably less code. I am sure there are ways to make my Angular examples smaller, but the way that I wrote them is probably how 98% of all Angular developers would create them.
In another post and conclusive part II, we will be looking at moving over Angular directives being used as custom attributes.
I currently write Angular, but am not tied to it blindly – that said, I find Aurelia’s syntax to be just as cryptic as AngularJS. So I don’t really get the “As you can see, Angular is a horrible mess.” I don’t see a horrible mess, I see clearly defined controllers and directives. With Aurelia, I see “import this”, “require from that”. Why do we need these imports everywhere? So every custom Aurelia element must utilize the same code? import {customElement, bindable, bindingMode} from ‘aurelia-framework’;?
Seems verbose and unncessary. With angular, I just need to write app.directive, and I’m off and running.