Upgrade Angular from 1.2 to 1.3

Things we had to change in order to upgrade from Angular 1.2.26 to 1.3.14

See Migrating from 1.2 to 1.3 and AngularJS Migration Guide 1.2 to 1.3 documents first.

Change version in bower.json

We use several angular libraries: angular, angular-animate, angular-resource, etc. We have changed the versions from 1.2.26 to 1.3.14 for all angular libraries. We also specified in the resolutions to use strict version 1.3.14 for any other libraries that depend on angular

1
2
3
"resolutions": {
"angular": "1.3.14"
}

Remove 3rd party bind once plugin

Loading the website after version upgrade immediately revealed a problem with bindonce plugin.

TypeError: undefined is not a function
    at e.directive.r.link (bindonce.min.js?version=185b26ab77830d1a2a331a4a6004581b8f945450:1)

Because Angular 1.3 includes bind once natively, we removed the plugin and switched a lot of code. The code search for pasvaz.bindonce dependency found only 4 directives that used this 3rd party module.

Use native Angular bind once

I searched all our directive templates for attribute directives that start with bo- letters.

Instead of bo- syntax in these cases

1
2
3
<span bo-text="something" class="fluid-column cell"></span>
<span bo-class="{ 'important':isImportant() }"></span>
<span bo-style="{ 'larger':larger }"></span>

We used native syntax

1
2
3
<span class="fluid-column cell">{{ ::something }}</span>
<span ng-class="::{ 'important':isImportant() }"></span>
<span ng-style="::{ 'larger':larger }"></span>

See a few examples of one time binding in AngularJS one-time binding syntax

Disable base ref requirement

We use HTML5 push state, and do not wanted to specify the base ref in every page. Thus we have disabled this requirement, see nobase article.

Changed number input formatters

Angular 1.3 enforces number input fields to be actual numbers (as opposed to strings). Thus we got a lot of exceptions of the form Uncaught Error: [ngModel:numfmt] Expected "-0.37" to be a number. The problem is the custom filters we applied to the input text field with type number returned a string view value.

To find the places with invalid filters, we paused the Chrome DevTools on unhandled exception and inspected the element variable to the number input field

1
2
3
4
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
// hover over element to see the code, including the id
...
}

We used numeral to format numbers, which converted them to strings. To allow using same format with the Angular 1.3 we convert the formatted string back into Number before returning

1
2
3
// return formatted number
var number = numeral(input);
return Number(number.format(formatStr));

or convert the input element from type="number" to type="text".

Setup properties for binding in unit tests

Some of our directives that use isolate scope had to be tested better. We used unit tests with default undefined parent scope properties when using two-way binding. This no longer works in angular 1.3 because one cannot two-way binding to non-existing expression, see error.

custom directive to be tested
1
2
3
4
5
6
{
// isolate scope with two-way binding to parent
scope: {
foo: '='
}
}

We used to unit test the above custom directive without creating "foo" property on the parent scope. I am using kensho/ng-describe to write unit tests.

angular 1.2 unit test
1
2
3
4
5
ngDescribe({
name: 'custom directive',
element: '<custom-directive></custom-directive>',
...
});

In Angular 1.3 binding to undefined nonassignable expression is invalid and we had to create explicit parent scope

angular 1.3 unit test
1
2
3
4
5
6
7
8
ngDescribe({
name: 'custom directive',
parentScope: {
foo: undefined
},
element: '<custom-directive foo="foo"></custom-directive>',
...
});

Other thoughts

The transition was very smooth with just a little bit of syntax change due to bind once transition.