After upgrading to Angular 1.3

New features to use right after upgrading to Angular 1.3 from 1.2

After you have upgraded from Angular 1.2 to 1.3 you need to use a few new features right away. These are simple to use and require only minimal code changes.

Turn on strict dependency injection

By default the AngularJS dependency injection reads the names of the things to inject from function's arguments list

1
2
3
4
angulr.module('MyModule')
.controller(function myController($scope, something, else) {
$scope.foo = something;
});

This quickly breaks when the code is minified and the argument names are shortened

minified
1
2
3
4
5
angulr.module('MyModule')
.controller(function myController(a, b, c) {
// Agular does not know what to inject for "a", "b" or "c"
a.foo = b;
});

To prevent injection errors after code minification you can use explicit annotations, passing names of the arguments as an array of strings. The minification step does not change strings, thus preserving injection targets.

1
2
3
4
5
6
7
8
9
10
11
// full source
angulr.module('MyModule')
.controller(['$scope', 'something', 'else', function myController($scope, something, else) {
$scope.foo = something;
}]);
// minified source
angulr.module('MyModule')
.controller(['$scope', 'something', 'else', function myController(a, b, c) {
// Angular knows that first argument to inject is '$scope', second is 'something', etc.
a.foo = b;
}]);

In Angular 1.2 you had no way of catching these errors unless you ran your unit tests against minified code too, and the unit tests had to cover every code path. There is ng-annotate module, but it is unreliable in detecting every method that needs to be wrapped in explicit annotations.

Luckily Angular 1.3 solves this problem. You can just flip the flag when bootstrapping your application and even unminified code will require explicit annotations, making finding problems earlier much simpler.

1
2
3
<body ng-app="MyModule" ng-strict-di>
...
</body>

That is it! If you bootstrap the application yourself, you can pass this as an option

1
angular.bootstrap(document.body, ['MyModule'], { strictDi: true });

Remove debug info

Have you noticed all these class="ng-scope" node attributes in your DOM? Angular adds these tags to enable testing tools like Protractor and Batarang to run. But these also take time to generate and put into DOM. Luckily these are simple to disable for extra performance boost.

1
2
3
app.config(['$compileProvider', function ($compileProvider) {
$compileProvider.debugInfoEnabled(false);
}]);

If you need debug info during debugging, like when executing Angular commands from the browser console or running code snippets, just execute the following call from the console first

1
angular.reloadWithDebugInfo();

The page will be reloaded with all the debug DOM attributes.

Watch multiple scope expressions

If you watch multiple scope properties, and execute a listener function, you might execute it multiple times. For example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$scope.minRent = 0;
$scope.maxRent = 10000;
$scope.minBedrooms = 0;
$scope.maxBedrooms = 5;
function filterProperties() {
console.log('filtering properties');
}
$scope.watch('minRent', filterProperties);
$scope.watch('maxRent', filterProperties);
$scope.watch('minBedrooms', filterProperties);
$scope.watch('maxBedrooms', filterProperties);
// when controller runs
filtering properties
filtering properties
filtering properties
filtering properties

This happens because filterProperties gets executed when each expression changes separately. Instead we are interested if any expression changes.

1
2
3
4
5
6
7
8
9
10
$scope.minRent = 0;
$scope.maxRent = 10000;
$scope.minBedrooms = 0;
$scope.maxBedrooms = 5;
function filterProperties() {
console.log('filtering properties');
}
$scope.watch(['minRent', 'maxRent', 'minBedrooms', 'maxBedrooms'], filterProperties);
// when controller runs
filtering properties

The callback in this case receives a single combined list of changes as an array

1
2
3
filterProperties(/* new values */ [0, 10000, 0, 5],
/* old values */ [undefined, undefined, undefined, undefined],
scope);

See $watchGroup for more details.

Use controllerAs AND bindToController to get rid of scopes

I hope by now you use as many isolate scopes as possible. I also hope that you use controllerAs syntax to prevent accidental scope property overrides (see Binding to Directive Controllers as source for the example below).

When using AngularJS 1.2 you had to watch isolate scope properties in order to update them on the controller and be able to use them for two way binding.

angular 1.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.directive('someDirective', function () {
return {
scope: {
name: '='
},
controller: function ($scope) {
this.name = 'Pascal';
$scope.$watch('name', function (newValue) {
this.name = newValue;
}.bind(this));
},
controllerAs: 'ctrl',
template: '<div> live name is {{ ctrl.name }} </div>'
};
});

We had to watch name because it was shared via two way binding from the parent scope and would not be reflected in the template using ctrl.name expression. The template shows ctrl.name which lives in the controller instance, which is separate from the $scope object.

In Angular 1.3 you can tell the $compile to actually bind isolate scope directly to the controller without you managing the updates yourself.

angular 1.3
1
2
3
4
5
6
7
8
9
10
11
12
13
app.directive('someDirective', function () {
return {
scope: {
name: '='
},
controller: function () {
this.name = 'Pascal';
},
controllerAs: 'ctrl',
bindToController: true,
template: '<div> live name is {{ ctrl.name }}</div>'
};
});

Any changes to name property in the parent scope will be reflected in the controller name property and then in the template. Same with the reverse - any updates to the ctrl.name will be automatically propagating back to the parent scope.

For more details on controllerAs and bindToController see Separate model from view in Angular.