Web Front-end13 minute read

Aurelia vs. Angular 2 — A Code Comparison

Angular and Aurelia are fierce competitors developed and released at approximately the same time. They have a similar philosophy, but they differ in a number of key ways. This article does side-by-side comparisons of those differences in features and code.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

Angular and Aurelia are fierce competitors developed and released at approximately the same time. They have a similar philosophy, but they differ in a number of key ways. This article does side-by-side comparisons of those differences in features and code.


Toptalauthors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.
Alexander Mikhalchenko
Verified Expert in Engineering
11 Years of Experience

Alexander is the full-stack developer you bring in to rescue a project with tight schedules. Usually, he prefers less extreme development.

Read More
Share

Angular and Aurelia, descendants of good old JavaScript Angular 1, are fierce competitors, developed and released at approximately the same time and with a similar philosophy, but differing in a number of key ways. In this article, we’ll do side-by-side comparisons of those differences in features and code.

Long story short, Aurelia was created by Rob Eisenberg, known as the creator of Durandal and Caliburn. He worked on the Angular 2 team at Google but left in 2014 when his views on how a modern framework should look differed from theirs.

The similarities persist on a more technical level, too: Templates and the components (or custom elements) associated with them are core to both Angular and Aurelia apps, and both require you to have a root component (i.e. the app). Furthermore, both Angular and Aurelia heavily use decorators for component configuration. Each component has a fixed lifecycle that we can hook into.

So What’s the Difference between Aurelia and Angular 2?

The key difference, according to Rob Eisenberg, is in the code: Aurelia is unobtrusive. When developing an Aurelia app (after configuration), you write in ES6 or TypeScript, and the templates look like absolutely sane HTML, especially when compared to Angular. Aurelia is convention over configuration, and 95% of the time you’ll be just fine using default conventions (such as template naming, element naming, etc.), while Angular requires you to provide configuration for basically everything.

Aurelia is also considered to be more standards compliant, if only because it is not case-sensitive when it comes to HTML tags, whereas Angular 2 is. This means that Angular 2 cannot rely on the browser’s HTML parser, so they created their own.

Another factor to consider when choosing between SPA-frameworks is the community- the ecosystem- around them. Both Angular and Aurelia have all the basics (router, template engine, validation, etc), and it’s easy to get a native modal or use some third-party library, but it comes as no surprise that Angular has a bigger community and bigger development team.

Furthermore, while both frameworks are open source, Angular is mainly developed by Google and is not intended to be commercialized while Durandal, Inc., which employs the core team, is following Ember.js’ model of monetization via consulting and training.

Aurelia vs. Angular: A Code Comparison

Let’s look at some of the most notable features which underline the philosophies behind each framework.

After cloning seed projects for Angular and Aurelia, we have a ES6 Aurelia app (you can use Jspm/System.js, Webpack and RequireJS in combination with ES6 or TypeScript) and a TypeScript Angular app (WebPack), respectively.

Let’s roll.

Data Binding

Before we compare side by side working examples, we have to take a look at some syntactical differences between Aurelia and Angular 2, namely in the key functionality of binding values from the controller to the view. Angular 1 used “dirty checking” for everything, a method which scanned the scope for changes. This, understandably, caused a number of performance issues. Neither Angular 2 nor Aurelia followed that path. Instead, they use event binding.

Data Binding in Angular 2

In Angular, you bind data with square brackets and use parentheses to bind events, like so:

<element [property]="value"></a>
<element (someEvent)="eventHandler($event)"></a>

Two-way binding—for when you want changes in application data to reflect on the view and vice versa—is a combination of both square and parenthesis brackets. So, for two-way bound input, this would work just fine:

<input type="text" [(ngModel)]="text">
{{text}}

In other words, parentheses represent an event while square brackets represent a value being pushed to input.

The Angular team did a great job separating binding directions: to the DOM, from the DOM, and bidirectional. There’s also a lot of syntax sugar related to binding classes and styles. Consider, for example, the following snippet as an example of one-way binding:

<div [class.red-container]="isRed"></div>   
<div [style.width.px]="elementWidth"></div> 

But what if we want to bind two-way data into a component? Consider the following basic input setup:

<!-- parent component -->
<input type="text" [(ngModel)]="text">
{{ text }}

<my-component [(text)]="text"></my-component>

import {Component, Input} from '@angular/core';

@Component(/* ... */)
export class MyComponent {
  @Input() text : string;
}

<!-- child component -->
<input [(ngModel)]="text">
Text in child: {{ text }}

Note that, to use ngModel, your module must import FormsModule from @angular/forms. Now we have something interesting. Updating the value in the parent input changes values everywhere, but modifying the child’s input only affects that child. If we want it to update the parent value, we need an event informing the parent. The naming convention for this event is property name + 'Change', like so:

import {Component, Input, Output, EventEmitter} from '@angular/core';

@Component(/* ... */)
export class MyComponent {
  @Input() text : string;
  @Output() textChange = new EventEmitter();

  triggerUpdate() {
    this.textChange.emit(this.text);
  }
}

The two-way binding starts working properly right after we bind to the ngModelChange event:

<!-- child component -->
<input [(ngModel)]="text" (ngModelChange)="triggerUpdate($event)">

What about one-time binding, when you effectively tell the framework to ignore the bound values even if they change?

In Angular 1, we used {{::value}} to bind one time. In Angular 2, one-time binding gets complicated: the documentation says that you can use the changeDetection: ChangeDetectionStrategy.OnPush attribute in the Component’s configuration, but that will make all your bindings one-time.

Data Binding: The Aurelia Way

In contrast to Angular 2, binding data and events in Aurelia is really simple. You can use either interpolation, just like Angular’s property="${value}", or use one of the following binding types:

property.one-time="value"
property.one-way="value"
property.two-way="value"

The names are self-explanatory. Furthermore, there’s property.bind="value", which is syntax-sugar that self-detects whether the binding should be one-way or two-way. Consider:

<!-- parent-->
<template bindable="text">
  <input type="text" value.bind="text"/>
  <child text.two-way="text"></child>
</template>

<!-- child custom element -->
<template bindable="text">
  <input type="text" value.bind="text"/>
</template>

In the above snippet, both @bindable and @Input are configurable, so you can easily change things like the name of the property being bound, etc.

What about events? To bind to events in Aurelia, you use .trigger and .delegate. For example, to have a child component fire an event, you might do the following:

// child.js
this.element.dispatchEvent(new CustomEvent('change', {
   detail: someDetails
}));

Then, to listen for that in the parent:

<child change.trigger="myChangeHandler($event)"></child>
<!-- or -->
<child change.delegate="myChangeHandler($event)"></child>

The difference between those two is that .trigger creates an event handler on that particular element whilst .delegate adds a listener on document. This saves resources, but it obviously won’t work for non-bubbling events.

A Basic Example of Aurelia vs. Angular

Now that we’ve covered binding, let’s create a basic component which renders a scalable vector graphic (SVG). It’ll be awesome, therefore we’ll call it awesome-svg. This exercise will illustrate both basic functionality and philosophy for Aurelia and Angular 2. This article’s Aurelia code examples are available on GitHub.

SVG Rectangles Example in Aurelia

Let’s first build out the JavaScript file:

// awesome-svg.js

import {bindable} from 'aurelia-framework';

export class AwesomeSvgCustomElement {
  @bindable title;
  @bindable colors = [];
}

Now for the HTML.

In Aurelia, you can specify the template (or use the inline one) with the annotations @template, @inlineView or even the @noView, but out of the box, it searches for the .html file with the same name as the .js file. The same is true for the custom element’s name—you can set it with @customElement('awesome-svg'), but if you don’t, Aurelia will convert the title to dash-case and look for a match.

Since we didn’t specify otherwise, the element will be called awesome-svg and Aurelia will search for the template with the same name as the js file (i.e. awesome-svg.html) in the same directory:

<!-- awesome-svg.html -->

<template>
  <h1>${title}</h1>
  <svg>
    <rect repeat.for="color of colors" 
          fill.bind="color" 
          x.bind="$index * 100" y="0" 
          width="50" height="50"></rect>
  </svg>
</template>

Notice the <template> tag? All templates need to be wrapped in a <template> tag. Also worth noticing is that you use ` for … of and the string interpolation ${title}` just like you do in ES6.

Now to use the component, we should either import it in a template with <require from="path/to/awesome-svg"></require> or, if it’s used across the app, globalize the resource in the framework’s configure function with aurelia.use.globalResources('path/to/awesome-svg');, which will import the awesome-svg component once and for all.

[Note, if you do neither of these, <awesome-svg></awesome-svg> will be treated just like any other HTML tag, without errors.]

You can display the component with:

<awesome-svg colors.bind="['#ff0000', '#00ff00', '#0000ff']"></awesome-svg>

This renders a set of 3 rectangles:

SVG Rectangles Example in Aurelia

SVG Rectangles Example in Angular 2

Now let’s do the same example in Angular 2, also available on GitHub. Angular 2 requires that we specify both the template and the element name:

// awesome-svg.component.ts

import {Component, Input} from '@angular/core';

@Component({
  selector: 'awesome-svg',
  templateUrl: './awesome-svg.component.html'
})
export class AwesomeSvgComponent {
  @Input() title : string;
  @Input() colors : string[] = []
}

The view is where things get a little bit complicated. First of all, Angular silently treats unknown HTML tags the same way a browser does: it fires an error saying something along the lines of my-own-tag is an unknown element. It does the same for any properties you bind, so if you had a typo somewhere in the code, it would attract major attention because the app would crash. Sounds good, right? Yes, because you’d notice right away if you broke the app, and no, because this is just bad form.

Consider this snippet, which is perfectly fine in terms of binding syntax:

<svg>
  <rect [fill]="color"></rect>
</svg>

Even though it reads fine, you’ll get an error like “can’t bind to ‘fill’ since it isn’t a known property of ‘:svg:rect’.” To fix this, you need to use the syntax [attr.fill]="color" instead. Also note that it’s required to specify namespace in child elements inside <svg/>: <svg:rect> to let Angular know this should not be treated as HTML. Let’s expand our snippet:

<!-- awesome-svg.component.html-->

<h1>{{ title }}</h1>
<svg>
  <rect
    *ngFor="let color of colors; let i = index"
    [attr.fill]="color"
    [attr.x]="i * 100" y="0"
    width="50" height="50"
  ></rect>
</svg>

There we go. Next, import it in the module configuration:

@NgModule({
  declarations: [ AwesomeSvgComponent ]
  //...
})

Now the component may be used across this module, like so:

<awesome-svg [colors]="['#ff0000', '#00ff00', '#0000ff']" title="Rectangles"></awesome-svg>

SVG Rectangles Example in AngularJS 2

Custom Elements

Suppose now we want our rectangle code to be a custom component with its own logic.

Custom Elements: The Angular 2 Way

Since Angular 2 renders components by what matches its defined selector, it’s extremely easy to define a custom component, like so:

@Component({
    selector: 'g[custom-rect]',
  ...
})

The above snippet would render the custom element to any <g custom-rect></div> tags, which is extremely handy.

Custom Elements: The Aurelia Way

Aurelia lets us create template-only custom elements:

<template bindable="colors, title">
  <h1>${title}</h1>
  <svg>
    <rect repeat.for="color of colors" 
          fill.bind="color" 
          x.bind="$index * 100" y="0" 
          width="50" height="50"></rect>
  </svg>
</template>

The custom element will be named with respect to the file name. The only difference from naming other components is that when importing, either in configure or via the <require> tag, you should put .html at the end. So for example: <require from="awesome-svg.html"></require>.

Aurelia has custom attributes, too, but they do not serve the same purpose as in Angular 2. For example, in Aurelia, you can use the @containerless annotation on the custom rect element. @containerless can also be used with custom templates without the controller and <compose>, which basically renders stuff into the DOM.

Consider the following code containing the @containerless annotation:

<svg>
  <custom-rect containerless></custom-rect>
</svg>

The output would not contain the custom element tag (custom-rect), but instead we get:

<svg>
  <rect ...></rect>
</svg>

Services

In regards to services, Aurelia and Angular are very similar, as you’ll see in the following examples. Suppose we need NumberOperator which depends on NumberGenerator.

Services in Aurelia

Here’s how to define our two services in Aurelia:

import {inject} from 'aurelia-framework';
import {NumberGenerator} from './number-generator'

export class NumberGenerator {
  getNumber(){
    return 42;
  }
}

@inject(NumberGenerator)
export class NumberOperator {
  constructor(numberGenerator){
    this.numberGenerator = numberGenerator;
    this.counter = 0;
  }

  getNumber(){
    return this.numberGenerator.getNumber() + this.counter++;
  }
}

Now, for a component, we inject in the same way:

import {inject} from 'aurelia-framework';
import {NumberOperator} from './_services/number-operator';

@inject(NumberOperator)
export class SomeCustomElement {
  constructor(numberOperator){
    this.numberOperator = numberOperator;
    //this.numberOperator.getNumber();
  }
}

As you can see, with dependency injection, any class can be a fully extensible service, so you can even write your own resolvers.

Factories in Aurelia

If what you need is a factory or a new instance (Factory and NewInstance are just a couple popular resolvers provided out of the box), you can do the following:

import { Factory, NewInstance } from 'aurelia-framework';

@inject(SomeService)
export class Stuff {
  constructor(someService, config){
    this.someService = someService;
  }
}

@inject(Factory.of(Stuff), NewInstance.of(AnotherService))
export class SomethingUsingStuff {
  constructor(stuffFactory, anotherService){
    this.stuff = stuffFactory(config);
    this.anotherServiceNewInstance = anotherService;
  }
}

Angular Services

Here’s the same set of services in Angular 2:

import { Injectable } from '@angular/core';
import { NumberGenerator } from './number-generator';

@Injectable()
export class NumberGenerator {
  getNumber(){
    return 42;
  }
}

@Injectable()
export class NumberOperator {

  counter : number = 0;

  constructor(@Inject(NumberGenerator) private numberGenerator) { }

  getNumber(){
    return this.numberGenerator.getNumber() + this.counter++;
  }
}

The @Injectable annotation is required, and to actually inject a service, you need to specify the service in the list of providers in the component configuration or the entire module configuration, like so:

@Component({
  //...
  providers: [NumberOperator, NumberGenerator]
})

Or, not recommended, you can also specify it in the bootstrap(AppComponent, [NumberGenerator, NumberOperator]) call.

Note that you need to specify both NumberOperator and NumberGenerator, regardless of how you inject it.

The resulting component will look something like this:

@Component({
  //...
  providers: [NumberOperator, NumberGenerator],
})
export class SomeComponent {
  constructor(@Inject(NumberOperator) public service){
    //service.getNumber();
  }
}
Factories in Angular 2

In Angular 2, you can create factories with the provide annotation, which is also used for aliasing services in order to prevent name collisions. Creating a factory might look like the following:

let stuffFactory = (someService: SomeService) => {
    return new Stuff(someService);
}

@Component({
  //...
  providers: [provide(Stuff, {useFactory: stuffFactory, deps: [SomeService]})]
})

Transclusion

Angular 1 had the ability to include content, a “slot,” from one template into another using transclusion. Let’s see what its descendants have to offer.

Content Projection with Angular 2

In Angular 2, transclusion is called “content projection,” and it works the same way that ng-transclude did; i.e., speaking in Angular 1 terms, the transcluded content uses the parent scope. It will match the transcluded content tag based on the configuration selector. Consider:

@Component({
  selector: 'child',
  template: `Transcluded: <ng-content></ng-content>`
})
export class MyComponent {}

You can then use the component with <child-component>Hello from Translusion Component</child-component>, and we’ll get the exact transcluded Yes HTML rendered in the child-component.

For multi-slot transclusion, Angular 2 has selectors that you can use in the same manner as you would for @Component configuration:

<!-- child.component.html -->
<h4>Slot 1:</h4>
<ng-content select=".class-selector"></ng-content>

<h4>Slot 2:</h4>
<ng-content select="[attr-selector]"></ng-content>
<!-- parent.component.html -->


<child>
  <span class="class-selector">Hello from Translusion Component</span>
  <p class="class-selector">Hello from Translusion Component again</p>
  <span attr-selector>Hello from Translusion Component one more time</span>
</child>

Slots in Angular2

You can use select on your custom tags, but remember that the tag must be known to Angular 2.

Slots with Aurelia

Remember when I said Aurelia follows web standards whenever possible? In Aurelia, transclusion is called slots, and it’s just a polyfill for Web Components Shadow DOM. Shadow DOM is not created for slots yet, but it follows W3C specs.

<!-- child -->
<template>
  Slot: <slot></slot>
</template>

<!-- parent -->
<template>
  <child>${textValue}</child>
</template>

Aurelia was designed to be standards-compliant, and Angular 2 wasn’t. As a result, we can do more awesome things with Aurelia’s slots, like use fallback content (trying to use fallback content in Angular 2 fails with <ng-content> element cannot have content). Consider:

<!-- child -->
<template>
  Slot A: <slot name="slot-a"></slot> <br />
  Slot B: <slot name="slot-b"></slot>
 Slot C: <slot name="slot-c">Fallback Content</slot>
</template>

<!-- parent -->
<template>
  <child>
    <div slot="slot-a">A value</div>
    <div slot="slot-b">B value</div>
</child>
</template>

In the same fashion as Angular 2, Aurelia will render all occurrences of the slot based on a name match.

Slots in Aurelia

It’s also worth noting that in both Aurelia and Angular, you can compile template parts and render components dynamically (using <compose> with view-model in Aurelia, or ComponentResolver in Angular 2).

Shadow DOM

Both Aurelia and Angular support Shadow DOM.

In Aurelia, just use the @useShadowDOM decorator and you’re ready to go:

import {useShadowDOM} from 'aurelia-framework';

@useShadowDOM()
export class YetAnotherCustomElement {}

In Angular, the same can be done with ViewEncapsulation.Native:

import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  //...
  encapsulation: ViewEncapsulation.Native,
})
export class YetAnotherComponent {}

Remember to check if your browser supports Shadow DOM.

Server-side Rendering

It’s 2017, and server-side rendering is very trendy. You can already render Angular 2 on the back-end with Angular Universal, and Aurelia will have this in 2017 as stated in its team’s New Year’s resolutions. In fact, there’s a runnable demo in Aurelia’s repo.

In addition to that, Aurelia has had progressive enhancement capabilities for over a year, something that Angular 2 does not because of its non-standard HTML syntax.

Size, Performance, and What’s Coming Next

While it doesn’t show us the whole picture, DBMonster benchmarks, with default configurations and optimized implementation, paints a good comparison picture: Aurelia and Angular show similar results of approximately 100 re-renders per second (as tested on a MacBook Pro), while Angular 1 showed something around half of that result. Both Aurelia and Angular outperform Angular 1 by about five times, and both are 40% ahead of React. Neither Aurelia nor Angular 2 have a Virtual DOM implementation.

On the issue of size, Angular is roughly twice as fat as Aurelia, but the guys at Google are working on it: Angular’s roadmap includes releasing Angular 4 with plans to make it smaller and more lightweight while improving developer experience. There’s no Angular 3, and really, the version number should be dropped when talking about Angular, since major releases are planned every 6 months. If you look at the path Angular took from alpha to its current version, you’ll see that it wasn’t always coherent with things like attributes being renamed from from build to build, etc. The Angular team promises that breaking changes will be easy to migrate.

As of 2017, the Aurelia team plans to release Aurelia UX, provide more integrations and tools, and implement server-side rendering (which has been on the roadmap for a very long time).

Angular 2 vs. Aurelia: A Matter of Taste

Both Angular and Aurelia are good, and choosing one over another is a matter of personal taste and priorities. If you need a more slender solution, Aurelia is your best option, but if you need community support, Angular is your winner. It’s not a question of “Does this framework allow me to…?” because the answer is simply “yes.” They provide roughly the same functionality while following different philosophies and styles along with a completely different approach to web standards.

Hire a Toptal expert on this topic.
Hire Now
Alexander Mikhalchenko

Alexander Mikhalchenko

Verified Expert in Engineering
11 Years of Experience

Minsk, Minsk Region, Belarus

Member since November 27, 2016

About the author

Alexander is the full-stack developer you bring in to rescue a project with tight schedules. Usually, he prefers less extreme development.

Read More
authors are vetted experts in their fields and write on topics in which they have demonstrated experience. All of our content is peer reviewed and validated by Toptal experts in the same field.

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

Join the Toptal® community.