The Angular 1.4 release introduced a lot of breaking changes. You might have had trouble upgrading your 1.3 apps (if you even decided to try).
There’s a good run-down on how to migrate your code in the Angular docs. We’ll cover some of the same ground here but also dive into some extra approaches for adjusting your code, as well as some cases I’ve run into where I’ve had to support both 1.3 and 1.4.
Jump to a section to read the changes.
$animate
JavaScript and CSS animations can no longer be run in
parallel.
In older versions, if both JS and CSS animations were detected they would be run at the same time. This
won’t happen automatically any more, but you can use
$animateCss
to create CSS-based animations in your code.
The function params for
$animate.enabled()
when an
element is used are now flipped. This fix allows the function to act as
a getter when a single element param is provided.
I ran into this guy when trying to upgrade UI-Grid to Angular 1.4. The fix is to just swap the order of your parameters, unless you have to support multiple versions. In that case you’ll have to do some fun (not) version checking:
1
2
3
4
5
6
|
if (angular.version.major
> 1 || (angular.version.major
=== 1 && angular.version.minor >= 4)) {
$animate.enabled($elm, false);
}
else {
$animate.enabled(false,
$elm);
}
|
In addition to disabling the children of the element,
$animate.enabled(element, false)
will now also disable animations on
the element itself.
Just something to keep in mind. It’s unlikely that you were implicitly relying on this, but it could bite you.
Animation-related callbacks are now fired on
$animate.on
instead of directly being on the element.
If you were listening for events on your element with something like
element.on('$animate:before', ...)
you’ll need to switch to
$animate.on(element, 'enter', ...)
.
There is no need to call
$scope.$apply
or$scope.$digest
inside of an animation promise callback anymore since the promise is resolved within a digest automatically (but a digest is not run unless the promise is chained).
Yay! This makes things a bit cleaner. It wouldn’t really hurt to keep any remaining
$timeout
s in your animation callbacks, but you don’t need them to trigger a
$digest
anymore.
When an enter, leave or move animation is triggered then it will always end any pending or active parent class based animations (animations triggered via ngClass) in order to ensure that any CSS styles are resolved in time.
This will probably help you out by not having multiple animations running when you don’t intend them.
Prior to this fix there were two ways to apply CSS animation code to an anchor animation. With this fix, the suffixed CSS -anchor classes are now not used any more for CSS anchor animations.
Animation anchoring lets you pair up elements that are in different structural parts of the application, like views, so that it appears that a single element is transitioning. You can read about it and see a demo in the Angular docs.
With this change you can just specify an .ng-anchor
CSS class for your element (like
.my-element.ng-anchor
rather than having to make -anchor
-suffixed extra rules.
Also note that you can use .ng-anchor-in
and .ng-anchor-out
if you want to
target those phases of the animation.
If your CSS code made use of the
ng-animate-anchor
CSS class for referencing the anchored animation element then your
code must now useng-anchor
instead.
They just renamed the class. If you want to support both 1.3.x and 1.4.x you could add an extra rule
for.ng-anchor
anywhere you have .ng-animate-anchor
.
Partially or fully using a regex value containing
ng-animate
as a token is not allowed anymore. Doing so will trigger a
minErr exception to be thrown.
This refers to using the classNameFilter()
method in
$animateProvider
;
ng-animate
is essentially a reserved word. You can’t do this:
1
|
$animateProvider.classNameFilter(/my-animations
ng-animate/);
|
but you could do this if you wanted to:
1
|
$animateProvider.classNameFilter(/ng-animate-mine/);
|
$animateCss
The
$animateCss
service will now always return an object even if the animation is not set to run.
You won’t need to check and see if $animateCss(element, { ... });
has returned
anything any more.
$cookies
$cookies
no longer exposes properties that represent the current browser cookie values. Now you must explicitly the methods described above to access the cookie values. This also means that you can no longer watch the$cookies
properties for changes to the browser’s cookies.
The $cookies
API
originally polled for changes, synchronizing the local set with the browser. This was expensive and had
out-of-sync issues.
Now you need to use the API methods to access the cookies:
1
|
var allCookies = $cookies.getAll();
|
filter
Previously, the filter was not applied if used with a non array. Now, it throws an error. This can be worked around by converting an object to an array, using a filter such as https://github.com/petebacondarwin/angular-toArrayFilter
The “filter” filter would fail silently if you used it on an object. As explained in the initial bug report this would fail silently:
1
|
<div
ng-repeat="item in object | filter:{display:true}">stuff</div>
|
You’d just see stuff
repeated twice in your DOM with no explanation.
With 1.4.0 you’ll now get an error that looks like this: Expected
array but received: {“display”:false,”foo”:”bar”}. Using PBD’s filter is a good way to make sure you’re using the
proper object type with filter
.
$http
transformRequest
functions can no longer modify request headers … This behavior was unintended and undocumented, so the change should affect very few applications
The $http
property transformRequest can
be used to alter a given request’s http request body. If you want to modify headers, the changelog
says you can do it in your $http
request with a headers function:
1
2
3
4
5
6
7
|
$http.get(url, {
headers: {
'X-Custom-Header': function(config) {
return 'my-custom-value';
}
}
});
|
Or if you want more global alteration you can modify $httpProvider
‘s
header defaults:
1
|
$httpProvider.defaults.headers.common['X-Custom-Token'] = 'abc123';
|
limitTo
limitTo changed behavior when limit value is invalid.
Instead of returning empty object/array it returns unchanged input.
The limitTo filter creates a
subset of an array, much like slice
in vanilla JS. If you supply a bad value to limitTo
, like a string or NaN, you’ll now
get the unaltered array back rather than an empty array.
ngMessages
The
ngMessagesInclude
attribute is now its own directive and that must
be placed as a child element within the element with the ngMessages
directive.
Angular 1.3 introduced a nicer way to display form validation messages called ngMessages
.
You can read a nice intro in Pascal Precht’s blog
post.
When displaying validation messages you would often need to use the same message over and over. This
could lead too a less than DRY situation in your app.
ngMessageInclude
was a way to write a template for a validation message once and then
include it in multiple places. It still exists; it’s just a
directive instead of an attribute now:
1
2
3
4
5
6
7
8
9
10
|
<script type="script/ng-template" id="required-message">
<ng-message
when="required">
This field is required!
</ng-message>
</script>
<ng-messages for="myForm.username.$error">
<div ng-message="minlength">...<div>
<div ng-messages-include="required-message"></div>
</ng-messages>
|
You can read more about the change in Pasca’s follow-up on ngMessages.
ngOptions
When using
ngOptions
: the directive applies a surrogate key as the value of the<option>
element. This commit changes the actual string used as the surrogate key. We now store a string that is computed by callinghashKey
on the item in the options collection; previously it was the index or key of the
item in the collection.
This shouldn’t have too much effect on you, as you shouldn’t be relying on
ngOptions
‘ surrogate key. If you do then you’ll want to either add a
track by
expression to control how the surrogate key is defined, or just access the
property that the select’s ng-model
is bound to.
Be Aware: If you were relying on the actual value of a select box in your unit tests,
you’ll need to adjust for this. We ran into this with UI-Grid. Instead of
$('.my-select-box').val()
returning 5
, you’ll get
"number:5"
.
When iterating over an object’s properties using the
(key, value) in obj
syntax
the order of the elements used to be sorted alphabetically. … in practice this is not what people want and so this change iterates over properties in the order they are returned by Object.keys(obj).
If you were relying on this implicit ordering in ngOptions
you’ll need to add
a sorting filter.
Although it is unlikely that anyone is using it in this way, this change does change the behaviour of
ngOptions
in the following case:
- You are iterating over an array-like object, using the array form of the
ngOptions
syntax (item.label for item in items
) and that object contains non-numeric property keys.In this case these properties with non-numeric keys will be ignored.
Hopefully you would not treat yourself (or your coworkers) so villainously, but there’s probably
at least one person out there who has, at least unintentionally. If you find code with arrays that
have extra properties and the repeater is iterating over them in this way, change it to the object
iterator syntax: value.label for (key, value) in items
.
ngRepeat
Previously, the order of items when using
ngRepeat
to iterate
over object properties was guaranteed to be consistent by sorting the
keys into alphabetic order.Now, the order of the items is browser dependent based on the order returned from iterating over the object using the
for key in obj
syntax.
This is the same as the breaking change to ngOptions
above. If you were relying on
implicit sorting in ngRpeat when using an
object as the source, then you’ll need to add your own sorting filter. Otherwise the elements in
your repeater will come out in whatever order the user’s browser decides on.
Be Aware: The changelog says “…the best approach is to convert Objects into Arrays by a filter such as https://github.com/petebacondarwin/angular-toArrayFilter or some other mechanism, and then sort them manually in the order you need.”