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
// A Hello controller
export class Hello {
constructor (userService) {
this.userService = userService;
this.greeting = "Hello, " + this.userService.getUser() + "!";
}
};
user-service.js
// A User Service
export class UserService {
getUser () {
return "Newman";
};
};
index.js
import {Hello} from 'hello';
import {UserService} from 'user-service';
// No matter how or where we declare our objects,
// we'll always have to use Angular's registration code
// to let AngularJS know about them.
angular.module('App', []);
.controller('HelloCtrl', Hello)
.service('UserService', UserService)
... and so on
hello.html
<div data-ng-controller="HelloCtrl as hello">
<h1></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
// A Hello controller
export class Hello {
constructor (userService) {
this.userService = userService;
this.greeting = "Hello, " + this.userService.getUser() + "!";
}
};
user-service.js
// A User Service
export class UserService {
getUser () {
return "Newman";
};
};
index.js
// We don't need to explicitly register our objects with
// Aurelia - so really, we don't even need this.
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 !</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) {
// Assume we're using jQueryUI autocomplete.
$('#framework-input').autoComplete(['AngularJS', 'Aurelia', 'VanillaJS']);
},
controller: function ($scope) {
$scope.choose = function () {
// Log our preference somewhere.
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 () {
// Assume we're using jQueryUI autocomplete.
$('#framework-input').autoComplete(['AngularJS', 'Aurelia', 'VanillaJS']);
}
choose () {
// Log our preference somewhere.
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 () {
// some asynchronous activity that updates scope, such as a
// timeout or an interval
$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.
Continue reading %Aurelia vs Angular — a Feature by Feature Comparison%
by Jedd Ahyoung via SitePoint
No comments:
Post a Comment