Update 23.07.2016: Several people have commented on the fact that this article compares Aurelia to Angular 1.x instead of Angular 2; this is by design. While it’s true that Aurelia vs Angular 2 would be a more even comparison, this article is intended for people and companies who have currently committed to Angular 1.x and are looking for reasons to migrate to a newer framework, but are unsure about migrating to Angular 2 and need to justify the cost/benefit ratio of changing frameworks. I wanted to provide a detailed comparison to help those users and companies make that decision.

This article was peer reviewed by Vildan Softic. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

In the world of web development and JavaScript, we’ve seen a lot of paradigms come and go. But one paradigm has stuck around: the single-page web application.

One of the most popular frameworks to land in the past six years was AngularJS. Released in 2010 and backed by Google, it promised quick, easy development of rich, client-side applications through the use of declarative two-way data binding. Gaining a large following, AngularJS quickly established itself as a go-to methodology for web application development, being used by companies like Amazon and Pluralsight.

Today, in 2016, AngularJS has begun to show its age. A new framework, named Aurelia, has become a popular choice for rich, client-side applications. Primarily created by Rob Eisenberg of Durandal Inc., Aurelia targets the same problem space as AngularJS. However, Aurelia uses a modern approach to ease development and solve a lot of the problems that plagued AngularJS.

In this article, we’ll take a detailed look at AngularJS and Aurelia, and compare and contrast the two frameworks. For the purposes of this comparison, we’ll exclude Angular 2 from our framework comparison. Instead, we’ll only be focusing on the AngularJS 1.x framework. Today, using the perspective of a developer in 2016, we’ll take an apples-to-apples comparison of the AngularJS methodologies designed in 2010 and the modern methodologies used by Aurelia.

The Rundown

Both AngularJS and Aurelia are client-side JavaScript frameworks targeted at creating single-page web applications. Both AngularJS and Aurelia support intuitive, two-way data binding, client-side routing, and advanced templating features. Both AngularJS and Aurelia encourage extending HTML using custom elements. Both AngularJS and Aurelia ship with default components that wrap common functionality. As stated before, AngularJS and Aurelia target the same problem domain. So where do the similarities end?

Let’s take a quick look at the main differences between AngularJS and Aurelia.

AngularJS Aurelia Proprietary Standards-compliant Configuration Convention Complex Simple Expensive Efficient Fixed Flexible Monolithic Modular

Whoa — wait a minute. You might be saying, hey — it looks like you’ve stacked the deck a little bit there. But I’d like to delve more into these claims.

Proprietary (AngularJS) vs Standards-compliant (Aurelia)

Web standards have evolved in the six years since AngularJS was released. While AngularJS was initially designed to adhere to the standards of the time, it was forced to create many proprietary solutions for scenarios that didn’t have well-defined rules. Among these were JavaScript language standards and HTML templating.

JavaScript language standards

JavaScript’s language and ecosystem are constantly moving forward; its standards, features, and syntax are continually evolving. While AngularJS was designed to take advantage of web browser capabilities in 2010, Aurelia has been designed on top of modern standards.

AngularJS provided a non-standard JavaScript module format implementation which was designed to be used with the AngularJS framework. Aurelia, by comparison, leans on the ES2015 module standard. Additionally, Aurelia takes advantage of new language constructs — such as ES2016 decorators — to ease development and support emerging standards.

HTML templating

Both AngularJS and Aurelia allow you, as a developer, to extend HTML in new ways. When AngularJS was created, standards for extending HTML had not matured. Therefore, AngularJS created proprietary solutions for templating and custom elements.

Today, the Web Component spec defines a set of rules for both templating and custom elements. Aurelia actively adheres to these standards, supporting Shadow DOM, the <template> element, HTML imports, and native custom elements.

Configuration (AngularJS) vs Convention (Aurelia)

When I first started playing around with Angular, I thought it was awesome. Learning how to configure AngularJS with its specific code calls didn’t take a lot of time. However, as I grew more comfortable with AngularJS and built more applications, all of Angular’s configuration began to get in the way.

AngularJS requires you to create an Angular-specific module. Essentially, everything that your application will use must be explicitly registered with the framework, and configured, before the web application has started. As such, it’s necessary to attach all controllers, services, and custom directives to an AngularJS module before they can be used. Additionally, AngularJS controllers are coupled to views via code: a view must declare the controller it intends to use. All of this results in a lot of boilerplate. Let’s look at an example, using ES2015.

hello.js

export class Hello { constructor ( userService ) { this . userService = userService ; this . greeting = "Hello, " + this . userService . getUser ( ) + "!" ; } } ;

user-service.js

export class UserService { getUser ( ) { return "Newman" ; } ; } ;

index.js

import { Hello } from 'hello' ; import { UserService } from 'user-service' ; angular . module ( 'App' , [ ] ) ; . controller ( 'HelloCtrl' , Hello ) . service ( 'UserService' , UserService ) ... and so on

hello.html

< div data-ng-controller = " HelloCtrl as hello " > < h1 > {{hello.greeting}} </ h1 > ...my view </ div >

In comparison, Aurelia requires no explicit registration of components before they can be used. The framework knows how to find views and viewmodels without them having to be explicitly configured by using a default convention. (This convention can be overridden if necessary through configuration, but explicit configuration is not mandatory.) Finally, Aurelia viewmodels are not coupled to views by code inside the view.

hello.js

export class Hello { constructor ( userService ) { this . userService = userService ; this . greeting = "Hello, " + this . userService . getUser ( ) + "!" ; } } ;

user-service.js

export class UserService { getUser ( ) { return "Newman" ; } ; } ;

index.js

hello.html

< template > < h1 > ${greeting} </ h1 > ...my view </ template >

This means that getting started with Aurelia is easy: there’s less framework-specific code for a developer to learn to use. Aurelia’s out-of-the-box conventions support rapid development and decrease the learning curve. However, after getting more familiar with Aurelia, you can change its conventions if you wish — and if you don’t, there’s simply less framework-specific code to deal with.

Complex (AngularJS) vs Simple (Aurelia)

In my experience with AngularJS, while some of the basic concepts can be fairly simple, the advanced concepts are structurally and semantically complex. Some things (like writing extensible components and modules) aren’t too bad, while other things (complex directives) can be almost arcane. Aurelia aims to simplify the execution of its advanced concepts, creating a flatter learning curve.

Semantics

AngularJS uses complex semantics. A developer has to know them in order to really utilize the framework. For instance, in AngularJS, you can declare a service , a factory , a value , or a constant : AngularJS makes a distinction between all of these. You can also declare a controller , and a directive . Unfortunately, few of these share the same conventions — especially AngularJS directives.

Directives are a powerful construct in AngularJS — allowing applications to extend HTML with custom elements, attributes, and behavior. Unfortunately, they are also an advanced concept and they can have a steep learning curve.

Explaining AngularJS directives is beyond the scope of this article, but trust me on this one. Let’s just take a look at a sample directive.

index.html

< body ng-controller = " MainCtrl " > < h1 > What's your favorite Javascript framework? </ h1 > < choose-framework > </ choose-framework > </ body >

chooseFramework.html

< div > < input id = " framework-input " type = " text " ng-model = " framework " placeholder = " Choose a framework " /> < button data-ng-click = " choose() " > Choose </ button > < p ng-if = " chosen " > You prefer {{chosenFramework}}! </ p > </ div >

chooseFramework.js

app . directive ( 'chooseFramework' , function ( ) { return { scope : { framework : '' , chosen : false , } , restrict : 'E' , replace : true , templateUrl : 'chooseFramework.html' , link : function ( scope , elem , attrs ) { $ ( '#framework-input' ) . autoComplete ( [ 'AngularJS' , 'Aurelia' , 'VanillaJS' ] ) ; } , controller : function ( $scope ) { $scope . choose = function ( ) { alert ( 'Your framework choice has been stored for posterity.' ) ; $scope . chosenFramework = $scope . framework ; $scope . chosen = true ; } } } ; } ) ;

Aurelia, in contrast, simplifies all of these semantics and reduces the learning curve. It gets rid of the declaration step entirely, allowing you to inject your code as a dependency in a much simpler manner. Further, Aurelia uses well defined lifecycle methods instead of events, so code conventions are shared between viewmodels and custom elements. This makes writing and reasoning about code simple. Finally, arcane AngularJS directive declarations are replaced by custom elements that work in the same way as Aurelia viewmodels do.

Let’s take a look:

index.html

< body > < h1 > What's your favorite Javascript framework? </ h1 > < choose-framework > </ choose-framework > </ body >

chooseFramework.html

< div > < input id = " framework-input " type = " text " value.bind = " framework " placeholder = " Choose a framework " /> < button click.delegate = " choose() " > Choose </ button > < p if.bind = " chosen " > You prefer ${chosenFramework}! </ p > </ div >

chooseFramework.js

@ customElement ( 'choose-framework' ) export class ChooseFramework { constructor ( ) { this . framework = '' ; this . chosen = false ; } attached ( ) { $ ( '#framework-input' ) . autoComplete ( [ 'AngularJS' , 'Aurelia' , 'VanillaJS' ] ) ; } choose ( ) { alert ( 'Your framework choice has been stored for posterity.' ) ; this . chosenFramework = this . framework ; this . chosen = false ; } }

Interoperability

Because of the way its change detection works, AngularJS can’t detect changes to objects or properties that the framework itself doesn’t make. Essentially, if a change happens outside the AngularJS digest cycle, it must be notified so that it can pick it up. In practice, while AngularJS provides some service wrappers for common functionality (like timeouts, intervals, and promises) out of the box, this means that any third-party libraries that make changes must be wrapped to notify AngularJS that a change happened. You end up writing a lot of boilerplate code like this:

$scope . $apply ( function ( ) { $scope . value = 'updated' ; } ) ;

(After you realize this, you’re okay — but before you know what’s going on, you can easily run into this pitfall, as I did here. After realizing, though, you’ll end up writing this code a lot.)

Aurelia doesn’t require these wrappers, meaning a reduced footprint and simpler conventions. It also means that integrating third-party components and libraries is a lot easier.

Expensive (AngularJS) vs Efficient (Aurelia)

If you’ve used AngularJS in any significant way, you may have run into performance issues — especially when using ng-repeat with a very large list. AngularJS’s performance is hindered by its change detection method: dirty checking.

Angular’s change detection relied on a “digest cycle”. Essentially, AngularJS would define a time interval, and at the end of each interval, it would “digest” all of the changes that happened since the last digest. This happened multiple times per second. While this approach worked, it had three main drawbacks. First, every property would be checked every time even if no change occurred; second, it required constant CPU activity; and finally, the time-based digest cycle would start to bog down when a lot of properties needed change detection.

Further, when AngularJS responded to a change event, it would update the DOM immediately for that change. Many different changes in one digest cycle would cause a lot of browser repaints, creating a bottleneck and hurting performance.

Aurelia’s change detection, in comparison, observes a property for an actual change, instead of scanning for changes at a set timed interval. By choosing this modern approach, Aurelia essentially sidesteps the three issues described above. Aurelia can use dirty checking, but it will only do so as an extreme fallback. In all other cases, Aurelia’s change detection won’t cause the performance issues that plagued AngularJS.

Finally, instead of updating the DOM per change, Aurelia batches all DOM updates using microtasks, drastically reducing the number of browser repaints necessary to fully update a view. This increases performance in both web browsers and mobile devices.

In practice, all of this makes Aurelia faster and more performant than AngularJS. In a web browser environment, this is important — but it’s even more important on mobile devices, where performance is a paramount concern. However, it also means that as a developer, you can do more with your application without running into performance blocks.

Fixed (AngularJS) vs Flexible (Aurelia)

In my experience with AngularJS, its fixed conventions worked well until you ran into a condition that the framework hadn’t anticipated. We’ll take a look at two major differences between the AngularJS and Aurelia implementation.

The window.angular object

With AngularJS, the framework assumes that there is a window object available: when the script is loaded, it will create a window.angular global variable. Aurelia’s approach discards the outdated global variable convention. Instead, the core framework library exports an Aurelia object that can be used in any instance.

Attaching a variable to window is not necessarily an unreasonable assumption; AngularJS and Aurelia are both web frameworks, so you’d probably be running them in a web browser, right?

In my experience, this isn’t necessarily the case. Unit tests and end-to-end testing in AngularJS require a testing framework like Karma along with AngularJS’s specific mock library; this can make tests heavy and unwieldy to set up. (I ran into this issue myself.) In comparison, because Aurelia is modular and does not require window and document to be present, testing becomes simpler as a result.

As a bonus, isomorphic JavaScript becomes a possibility in Aurelia, while the default AngularJS implementation would never allow it. This also means that we can create nested Aurelia applications — something that took some creative coding in AngularJS.

Application configuration

When creating a web application in AngularJS, the framework and all providers must be configured before AngularJS bootstraps the application. Configuration after AngularJS has bootstrapped is not supported, due to the way the framework is architected. Thus, after your web application has started, your configuration is fixed and cannot be changed.

Aurelia, by comparison, allows dynamic configuration during runtime. It does have a convention for configuring the application at bootstrap, but the configuration is not static. This allows your configuration to be flexible, adjusting to the needs of your application.

One practical example of this is the $http service configuration in Angular, versus the HTTP service configuration in Aurelia. Both frameworks allow a developer to create “interceptors” — middleware that can transform an incoming or outgoing AJAX call. However, AngularJS requires that these interceptors are defined before the application starts — meaning that they cannot be removed at runtime. (This is actually a real-world scenario that people have run into.)

Monolithic (AngularJS) vs Modular (Aurelia)

Have you, as a developer, ever used a framework that only works with itself? In my experience with monolithic frameworks, development within the confines of the framework would be a breeze. But once you ever needed to break out of the mold, or reject its opinions, you would have to fight the framework.

AngularJS was originally built as a monolithic framework. All of its components, such as its client-side routing, its templating, and its binding system, were wrapped into one large bundle. Not only did this mean that the entire bundle was always required (even for a simple application), but AngularJS’s monolithic architecture made it difficult to remove components and change them when necessary. (As an example, this was apparent with Angular’s router component.) While later releases of AngularJS mitigated this somewhat by modularizing certain features and services, the core framework itself remained a tightly-coupled, singular bundle.

Aurelia, in comparison, takes a more modern approach. While it’s a full framework, Aurelia is composed of a collection of libraries that work together using well-defined interfaces — so that it’s completely modular. This means that a web application only needs to include the dependencies that it needs. Further, though, it means that as long as implementations adhere to the defined interface, individual components can be changed or swapped with minimal fuss.

As an example, let’s take AngularJS’s native dependency injection. In my project, I have multiple AngularJS modules that export services with identical names, but I find out that the native injector uses a single namespace for all modules, meaning that registering services with the same name results in a collision with unexpected behavior. I’d like AngularJS modules to act as separate DI containers to avoid any injector collisions, so I write a dependency injection component that fixes the problem. Unfortunately, because AngularJS’s service layer is a core component of AngularJS, I can’t get rid of it or change it without changing AngularJS’s core. Unless I rebuild the framework or change my application code, I’m stuck.

In Aurelia, although dependency injection is a core component used by and within the framework, because it’s a modular component I can swap it out with my own implementation — as long as I adhere to Aurelia’s dependency injection interface. If I find a problem with the native injector, I can swap in my own dependency injection implementation without having to rebuild the framework, touch any application code, or worry about dependent components. From experience, being able to swap framework components is a really nice ability to have.

The Verdict

It’s time to step back and recap a little bit. We’ve taken a look at AngularJS vs Aurelia in the following areas:

AngularJS Aurelia Proprietary Standards-compliant Configuration Convention Complex Simple Expensive Efficient Fixed Flexible Monolithic Modular

… and, based on these comparisons, I can only come to one logical conclusion:

AngularJS Aurelia Old and Busted New Hotness

In this comparison, it may sound as though I’m picking on AngularJS. And honestly, I kind of am. But that’s not to say that AngularJS is a bad framework. I had a lot of fun learning it, and using it, and I was able to build some great things with it. (In fact, there’s no way that I’d be able to talk about AngularJS in such detail without having used it so much.) However, AngularJS has some issues that simply haven’t aged well in six years. The bottom line is this: taking AngularJS and Aurelia in an apples-to-apples comparison in 2016, Aurelia is simply fresher and juicier than AngularJS.

The truth of the matter is this: if you’re thinking about choosing AngularJS over a more modern framework like Aurelia, it might be prudent to take a step back and re-evaluate. Aurelia is not difficult to learn, has an active community, and should be somewhat familiar to AngularJS developers. But the truly wonderful thing about Aurelia is that, while it does provide sensible defaults, it won’t force its opinion down your throat — and in my experience, this means you can depend on Aurelia, instead of having it depend on you. The benefits of using a modern framework with a developer-centric approach are definitely worth the payoffs.

If you’re interested in getting to know Aurelia a little better, check out some of the following Aurelia resources.

Getting Started

Community