Having worked on web technologies for a few years now, I have some experience working with a few JavaScript frameworks and libraries. Surprisingly, AngularJS turned out to be a very different beast.
The superheroic Javascript MVW framework
AngularJS is a structural framework for building dynamic web applications. It is not a library. So, by the principle of 'inversion of control’, the developer is expected to code following certain rules and guidelines laid out by the framework.
Angular is opinionated about how a dynamic web application should be built. And almost all of the opinions are backed by strong design principles and justifications. It also simplifies the application development process by abstracting a lot of the internal complexity and exposing only what the application developer needs to know.
It is very helpful indeed if the framework guides developers through the entire journey of building an app: from designing the UI, through writing the business logic, to testing.— The Zen of Angular
The AngularJS journey can evoke mixed feelings. The learning curve is very different from other JS frameworks. The initial barrier to get started is very low. But once you start diving deep the learning curve suddenly becomes steep. Ben Nadel sums up his experience succintly through this time series:
Although I’m still in transit, my journey so far has been pretty similar, if not as dramatic. But there have been some key elements of Angular that have got me very interested. Here, I’ll try to cover a few of those aspects that I have begun to love in AngularJS.
The simplicity of data binding
In almost all cases, setting up data binding requires some amount of coding to bind the view and model together. The way in which Angular approaches two-way binding is pretty cool and intuitive. It saves you from writing considerable amount of boiler-plate code.
The model acts as the single source of truth for all the data in your application. The data binding directives ({{ }}) provided by Angular binds the model to the DOM seamlessly. Rest is magic!
The beauty lies in the simplicity:
The simplicity is achieved through some smart change detection implemented using 'Dirty checking’, as against traditional 'change listeners’ or 'accessors’ that are used in some other JS frameworks like Backbone. In simple terms, Angular remembers the bound value and compares it to the new value to detect any change. This, it does through an operation called
$digest
loop which loops through all the variables under watch in the current scope and its children to check if it is dirty.
With the dirty checking approach, the data models can be plain old JavaScript objects (POJOs or POJSOs), without the need for any getter/setter. This makes your code much simpler and cleaner.
There is a lot of discussion around performance impact of dirty checking as against other approaches, but Misko Hevery, the father of AngularJS, quashes those arguments in this stackoverflow response.
The complexity of the
$digest
loop can be seen from this comment in the $digest
method in angular.js source.// Insanity Warning: scope depth-first traversal
// yes, this code is a bit crazy, but it works and we have tests to prove it!
But like I said before, the beauty lies in the simplicity that abstracts the internal complexity!
In future, Object.observe() - the new ECMAScript 6 API under construction, will replace these custom change detection mechanism. The native implementation should make change detection much faster.
Update: 5th Sep 2014
What makes data binding in Angular even more powerful is the fact that the binding source can be an expression. Angular evaluates the expression between the double curly brace notation ({{ }}) before binding.
Here’s a quick example:
Well implemented MVC (or MVVM or MVP or MVW)
There has been a lot of debate over whether AngularJS implements MVC or MVVM or MVP. A pissed-off Igor Minar (Lead of AngularJS) decided to end the debate with this post in the AngularJS forum, deciding to call Angular a MVW framework: Model - View - Whatever!
I’d rather see developers build kick-ass apps that are well-designed and follow separation of concerns, than see them waste time arguing about MV* nonsense. And for this reason, I hereby declare AngularJS to be MVW framework - Model-View-Whatever. Where Whatever stands for “whatever works for you”.— Igor Minar
Many of the frameworks/libraries implementing MVC give you the option of defining the M, the V and the C, and ask you to tie them together yourself. AngularJS lets you focus only on separating out M-V-C (or VM), and internally takes care of the wiring. You also don’t need to inherit from any framework object to define your entities. It feels like a very natural approach to implementing MVC.
Angular is the only framework that doesn’t make MVC seem like putting lipstick on a pig.
The
$scope
object acts like a ViewModel which provides the data required for the view. The controller initializes the $scope
and provides functions to manage the behaviour. It does not store any state of its own. The view is the HTML that has the corresponding controller directive. Very clear separation of concerns!Declarative User Interface
Any application that can be written in JavaScript, will eventually be written in JavaScript.— Jeff Atwood
Web programming was becoming JavaScript heavy. Some frameworks took it a notch further by taking the convoluted approach of defining user interface procedurally in Javascript and in turn generating the HTML, entirely ditching the declarative nature of user interface definition using HTML. But over the last few years, HTML has back in-focus again - thanks to HTML5 and Web Components. After all, HTML is what the matters to the browser at the end of the day!
Being declarative, HTML feels more intuitive for defining the user interface. It is also easy to understand and manipulate. It provides a clear separation of the presentation layer from the imperative logic.
Angular is built around the belief that declarative code is better than imperative when it comes to building UIs and wiring software components together, while imperative code is excellent for expressing business logic.— The Zen of Angular
AngularJS fully leverages the declarative power of HTML for building the User Interface. It doesn’t stop there; It enhances HTML further by teaching it some new tricks. Angular uses concepts of custom element, templates, etc. to make HTML, and thereby building User Interface, more powerful and intuitive.
It also uses HTML to determine the execution of the application. By linking controllers to sections of HTML using special attributes, you can declaratively tell Angular which controller to use for which parts of the user interface. The framework will take care of the loading and linking.
Directives
Angular is what HTML would have been had it been designed for applications.
HTML is an awesome declarative language for building static content. But it doesn’t provide much support in building dynamic applications - hence the dependency on JavaScript. But what if HTML was given a boost with custom constructs.
HTML enhanced for web apps!
'Directives’ are Angular’s way of boosting HTML with additional functionality. It breaks the boundaries of traditional HTML elements and opens it up to infinite possibilities. The core concept behind this is 'custom elements’ driven by the 'Web Components’ initiative. But Angular likes to call it 'directives’.
AngularJS allows developers to build their own custom HTML constructs: elements, attributes, classes, etc. All the traditional DOM manipulation that is done to build something new is now captured in this concept called directives, thereby separating this out from the M-V-C and improving greatly improving the modularty of the application. Directives are re-usable elements that act as the building blocks for your application view.
I also find the usage of directives (if designed well) greatly enhances the readability of the HTML. Imagine HTML with some rich elements like
<colorpicker></colorpicker>
, <datepicker></datepicker>
, etc. instead of infinitely nested div elements.
Side-note: Polymer, another initiative driven by Google, is making the Web Components standard available to modern day browsers. I really hope to see some synergy between Polymer and AngularJS in the area of Web Components, instead of each taking a path of its own.
Dependency Injection
AngularJS has a built-in Dependency Injection mechanism that works with almost all the JavaScript constructs of Angular. All Angular objects have their dependencies injected, be it framework dependencies or custom built dependencies.
Angular has chosen dependency injection over AMD(Asynchronous Module Definition) patterns, as a design decision. And from whatever I’ve seen so far, it seems to have been a wise choice. It helps you compose your application by grabbing whatever you need, without worrying where it resides and how it should be created.
var app = angular.app('MyApp',[])
app.controller('MyCtrl', function($scope, $location, MyService) {
// Do what you need to do!
});
Here, you see that the controller 'MyCtrl’ is provided with all that it needs - framework services like the scope, $location service and also custom services like MyService. Creating these dependencies and managing them is the responsibility of an AngularJS sub-system called Injector. Later, I will post in detail on how the Injector sub-system of AngularJS works. For now, just admire the beauty of the design.
Dependency Injection is one of the design principles that I really like. It helps you compose a SPA (Single Page Application) better. I have personally used it and have found tremendous benefits, especially when it comes to testability.
Testability
It is a really, really good idea to regard app testing as equal in importance to app writing. Testing difficulty is dramatically affected by the way the code is structured.— The Zen of Angular
AngularJS doesn’t treat testing as an afterthought. It is built with testability in mind. It ensures applications built with this framework are unit testing ready. This is mainly possible because AngularJS puts together the application using Dependency Injection.
Any sub-system of your application can be tested by mocking the dependency that is injected into it. In fact, Angular provides a mock HTTP server that can be inject in places where $http is a dependency. It also clearly separates out the application logic from the DOM manipulations, making testing a lot easier.
These, in my opinion, are some of the best features of AngularJS. This doesn’t mean it is perfect. Angular has its own flaws in being highly opinionated and sometimes complicated. It is also built mainly for CRUD applications, and is not suited for games or graphics intensive applications. But the core design principles are fairly strong, which is why I think it is quite powerful and stands out in the web development world. Plus the backing from Google is giving it that extra push!
I’ll cling on to this topic for a few more posts, while I explore some of the intricacies of this superheroic JavaScript framework.
The code is full of what I call scope soup. What is Scope Soup you ask?
This is...
angular.module('app', [])
.controller('MyCtrl', function($scope, $http){
$scope.tagSearch = '';
$scope.tags = [];
$scope.getTags = function(){
$http.get('/api/tags/?search=' + $scope.tagSearch)
.success(function(data){
$scope.tags = data;
});
};
$scope.$watch('tagSearch', function(){
$scope.getTags();
});
// 1000 more lines of this mess
});
AAAAHHHHHH!!!! Somebody save me from the madness!
Scope Soup is when you build a tangled mess of Angular code that is completely coupled to
$scope
in really terrible ways. The unfortunate part is that you see a lot of examples like that. I've written code like that. But after a year and a half working on a massive Angular project, I can assure you there is a better way.
So without further ado, here are my five guidelines for avoiding Scope Soup in Angular.
1. Controllers Should Be Classes
Angular makes it really easy to add an inline controller as an anonymous function, but just because you can doesn't mean you should. Inline functions encourage you to shove everything into
$scope
, but defining your controller as a class and using the prototype to add functionality helps to keep you honest.
** Update ** A colleage of mine, correctly pointed out that the
this
pointer was refering to $scope
instead of the controller. I forgot to pull out a reference to the controller using a closure. You could also do this using the .bind()
method.//Don't do this
app.controller('MyCtrl', function($scope){
$scope.doStuff = function(){
//Really long function body
};
});
//Do this instead
var MyCtrl = function($scope){
var _this = this;
$scope.doStuff = function(){
_this.doStuff();
};
};
MyCtrl.prototype.doStuff = function(){
//Really long function body
};
MyCtrl.$inject = ['$scope'];
app.controller('MyCtrl', MyCtrl);
Defining your controllers in this way makes you think a little harder about just hanging everything off of
$scope
. Aside from that, if your controller has more than a couple of methods, tracing the code through well defined methods is a lot easier than trying to sift through a mess of Scope Soup.2. Keep Controllers Skinny With Services
Rest services are usually pretty simple, so it can be tempting to just use
$http
to get what you need right in your controller. However, it's much better to encapsulate those calls into a service.//Don't do this
// ng-click='getDetail()'
$scope.getDetail = function(){
$http.get('/api/item/detail/' + $scope.selectedItem.id)
.success(function(detail){
$scope.itemDetail = detail;
});
};
//Do this instead
// ng-click='getDetail(item)'
MyCtrl.prototype.getDetail = function(item){
this.itemService.getDetail(item)
.success(function(detail){
item.detail = detail;
});
};
Again, doing this will make your code easier to understand. Not only that but it will be much easier to test since you can mock the service and inject it into your controller. It helps you to avoid Scope Soup because you have to decouple your data services from your controller.
Pulling out data access into a service will also allow you to reuse that same code within directives, filters, other controllers, and other services.
3. Eliminate Scope Using The 'Controller As' Syntax
Angular 1.2 introduced the ability to publish your entire controller to
$scope
using the "Controller As" syntax. Using this you can completely eliminate the need to reference $scope
in your controller.
The syntax is pretty simple as well.
<!-- In Your Binding -->
<div ng-controller="MyCtrl as ctrl">
<span></span>
</div>
//In your route
$routeProvider
.when('/',{
templateUrl: 'foo.html',
controller: 'MyCtrl',
controllerAs: 'ctrl'
});
A controller should be pretty self contained, and this technique will also keep you honest and less likely to rely on properties being set in the parent scope which can lead to difficult to maintain code, and hard to track down bugs.
//Don't do this
app.controller('MyCtrl', function($scope){
$scope.name = 'Techno Fattie';
});
//Do this
var MyCtrl = function(){
this.name = 'Techno Fattie';
};
app.controller('MyCtrl', MyCtrl);
4. Eliminate Watches Using 'ng-change' And ES5 Properties
Getting rid of
$scope
completely might seem impossible if you are using explicit watches in your controllers, but in most cases you shouldn't need them.
If you have a watch set up to listen for a property change that originates from a form field, then ng-change is your best bet. It's important to note however, that ng-change requires ng-model, and does not fire a change event if the change originates from the controller. That being said, this is a pretty common scenario where I see people use a
$watch
instead of just using ng-change.//Don't do this
<input type="text" ng-model="name" />
app.controller('MyCtrl', function($scope){
$scope.$watch('name', function(){
//call some service here
});
});
//Do this
<input type="text" ng-model="ctrl.name" ng-change="ctrl.search(ctrl.name)" />
MyCtrl.prototype.search = function(name){
//call some service here
};
If you have some property that isn't bound to an input field, or is going to be updated from code, it may seem like a watch is your only choice. However, if you don't have to support IE8 or lower, then you can take advantage of ES5 properties to trigger functionality when something changes on your controller.
//Don't do this
app.controller('MyCtrl', function($scope){
$scope.$watch('selectedItem', function(){
//call some service here
});
});
//Do this instead
var MyCtrl = function(){
this._selectedItem = null;
};
Object.defineProperty(MyCtrl.prototype,
"selectedItem", {
get: function () {
return this._selectedItem;
},
set: function (newValue) {
this._selectedItem = newValue;
//Call method on update
this.onSelectedItemChange(this._selectedItem);
},
enumerable: true,
configurable: true
});
This might seem a bit verbose, but it does help you avoid Scope Soup by eliminating your need to use a
$watch
. Your controller is a little easier to test with one less dependency and it's a lot more reusable.5. Don't Use Scope To Pass Data Around
Scope can be a convenient way to pass data between two controllers, it might even feel a little ninja, but it will almost always end in tears and heavy drinking.
The problem with relying on values to be set from a parent controller, or relying on a child controller to set data back up on the parent scope, is that you end up with an implicit coupling that isn't easy to see at first. If I'm looking at my controller and see some random property being used, but it's never set, and doesn't exist in my view, then I have to go hunting around to find out where it came from.
Worse, it means the controller implementation is completely dependent on the ordering of the bindings in the view! I can't use or test those controllers independently of each other, because they are coupled together by
$scope
.//Don't do this
app.controller('Parent', function($scope){
var sharedObj = {
someProperty: 'foo'
};
$scope.sharedObj = sharedObj;
$scope.$watch('sharedObj.someProperty', function(){
//call some service here
});
});
app.controller("Child", function($scope){
$scope.$watch('sharedObj.name', function(){
$scope.myName = $scope.sharedObj.name;
});
});
//Do this instead
var Parent = function(sharedDataService){
sharedDataService.updateName('foo');
};
var Child = function(messageService){
sharedDataService.onNameChanged(this.nameChangedHandler);
};
Child.prototype.nameChangedHandler = function(){
this.myName = sharedDataService.getName();
};
Using a messaging paradigm to communicate between controllers will help you keep things clean, decoupled, and testable. It helps you to avoid Scope Soup because you aren't using
$scope
like a peeping tom... taking advantage of your position and knowledge in order to do something you know you probably shouldn't.
Some of you might be objecting right now that I could have done the same thing by using
$emit
and $broadcast
to accomplish the same thing, but I prefer to keep those details hidden for the same reason I keep $http
hidden behind a service layer. It also means I am not dependent on having a particular parent-child relationship configured in order to make this work. Another advantage is that my service could be doing a lot more stuff under the hood if needed. It could be calling a web service, or pushing out a message using Web Sockets, but the code in my controllers won't have to change