AngularJS: Did you know…? From 1.3 to 1.4, with some extra

This is a continuation of “AngularJS: Did you know…? From 1.0 to 1.3, part 1 and part 2” focusing on functionalities made available around version 1.3–1.4.

For those who did not see previous posts — this is a list of functionalities made available in AngularJS that you might have missed or were not announced in a clear way.

DISCLAIMER! This is not a change log or tutorial — it is just a list of features that might have been overlooked when working with Angular.

What features were introduced?

  1. You can now force usage of jqLite or specific version of jQuery in your application with ng-jq introduced
//force usage of jqLite
<html ng-app ng-jq>
//force usage of specific jQueryLin variable in window
<html ng-app ng-jq=”jQueryLib”>

See more in documentation.

I really RECOMMEND that you stop using automatic jQuery wrapping. Mainly because it is really confusing to use jQuery API by assuming element will expose it — in my opinion it is so much better to explicitly say in your component that jQuery will be used (if you need it). Additionally you will get performance benefit.

WARNING! This functionality is a bit confusing — it does not matter if “ng-jq” is defined on element next to ng-app. In fact it is not connected to bootstrapping at all! Angular will try to find ng-jq (and variants) using “document.querySelector” when loading angular.js, not when you bootstrap your application… So if you have your scripts in <head> for some reason, you need to define “ng-jq” on <html> tag because <body> and other elements will not be there yet…

2. Number filter now renders “\u221e” (∞) if value is infinity

3. “limitTo” will ignore limit if invalid value (return unchanged value)

4. Date filter and ngModelOptions now accepts timezones other than UTC (and GTM for ngModel); specify it as offset ‘+0500’

5. You can now bind scope to controller instance using a bit cleaner approach (old way still works though)

6. $cookies is now standard getter/setter = there is no “magic” happening behind stage to synchronise values and expose them directly on $cookies (and that is good)

The new API on $cookies is as follows:
• get
• put
• getObject
• putObject
• getAll
• remove

CAUTION! You might be using ngStorage module for local/session storage that aimed to provide similar functionality as initial version of ngCookies. As such it has similar issues — what it does is a deep comparison of stored data (using angular.equals) on pretty much every digest cycle. If you store something big in storage… it will cost you a lot. In such case go for getter/setter approach (like angular-local-storage or many others).

7. $cookieStore is now DEPRECATED, so stop using or write your own if needed

8. You can now use angular.merge — It does a deep copy of all properties from source to destination preserving properties in child objects

9. You can now write your own custom params serializers for $http methods
So now you can decide how params object will be represented in URL — you can do it by extending “config” of any $http method that accepts it, with “paramSerializer” key :

paramSerializer — {string|function(Object<string,string>):string} — A function used to prepare the string representation of request parameters (specified as an object). If specified as string, it is interpreted as function registered with the $injector, which means you can create your own serializer by registering it as a service. The default serializer is the $httpParamSerializer; alternatively, you can use the $httpParamSerializerJQLike

Or it can be done in config phase with defaults in $httpProvider:

module.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.paramSerializer = '$httpParamSerializerJQLike';
}]);

10. ngOptions now stores value type inside generated option

//before
<option value="1">a</option>
//now
<option value="string:a">a</option>

11. ngOptions when iterating Objects no longer iterates them alphabetically but with order of Object.keys(obj) (so basically same as in for..in = order is not guaranteed across browsers)

12. ngRepeat now iterates objects in order of „for key in obj” (again order is not guaranteed across browsers)

13. Directive “select” will now use strict comparison of the ngModel scope value against option values to determine which option is selected (so numbers are not compared to strings etc.)

14. You can now filter array like objects using filter called “filter”

function getArguments() {
return arguments;
}
var argsObj = getArguments({name: ‘Misko’}, {name: ‘Igor’}, {name: ‘Brad’});
var nodeList = jqLite(“<p><span>Misko</span><span>Igor</span><span>Brad</span></p>”)[0].childNodes;
function nodeFilterPredicate(node) {
return node.innerHTML.indexOf(“I”) !== -1;
}
//arguments are not really not an array, just array like
expect(filter(argsObj, ‘i’).length).toBe(2);
//string is array like
expect(filter(‘abc’,’b’).length).toBe(1);
//DOM nodes list is array like
expect(filter(nodeList, nodeFilterPredicate).length).toBe(1);

15. You can use .decorator helper in angular.module to register service decorators

angular.module(‘myApp’).decorator(‘myService’, function($delegate) {
$delegate.test = ‘bye’;
return $delegate;
});

16. You can use $anchorScroll to scroll to specified element

$anchorScroll(hash);

17. When using controller in directive and not specifying “require”, post link will by default get current directive controller in ctrl parameter

{
//require: 'myDirective' //as you can see it is commented out
link: function(scope, element, attrs, ctrl){} //yes we have controller here!
controller: function(){}
}

18. $timeout and $interval allow passing additional parameters that will be passed to callbackFunction

var task1 = jasmine.createSpy(‘task1’);
$interval(task1, 1000, 2, true, ‘Task1’, ‘Task2’);
$window.flush(1000);
expect(task1).toHaveBeenCalledWith(‘Task1’, ‘Task2’);
var task2 = jasmine.createSpy(‘task2’);
$timeout(task2, 1000, true, ‘Task1’, ‘Task2’);
$window.flush(1000);
expect(task2).toHaveBeenCalledWith(‘Task1’, ‘Task2’);

19. ngClass allows both strings and objects in array (so that you can specify conditional classes when using array)

<div ng-class=” [classVar, {‘is-active’: activeVar }] “>

20. ngMessage directive now supports a new attribute called ng-message-exp which will evaluate an expression and will keep track of that expression as it changes in order to re-evaluate the listed messages

<!-- using attribute directives -->
<ANY ng-messages=”expression”>
<ANY ng-message-exp=”expressionValue”>…</ANY>
</ANY>
<!-- or by using element directives -->
<ng-messages for=”expression”>
<ng-message when-exp=”expressionValue”>…</ng-message>
</ng-messages>

With this you can loop ng-messages in ng-repeat!

21. ngMessagesInclude attribute has now been removed. Instead, ngMessagesInclude is to be used on its own element inline with other inline messages situated as children within the ngMessages container directive

<!-- AngularJS 1.3.x -->
<div ng-messages=”model.$error” ng-messages-include=”remote.html”>
<div ng-message=”required”>Your message is required</div>
</div>
<!-- AngularJS 1.4.x -->
<div ng-messages=”model.$error”>
<div ng-message=”required”>Your message is required</div>
<div ng-messages-include=”remote.html”></div>
</div>

22. The legacy methods 'success' and 'error' on promises returned by $http are now DEPRECATED.

They are still in use and controlled by “useLegacyPromiseExtensions” that defaults to “true”, but will be set to “false” soon and then removed entirely.

23. In inputs with ngModel, ngPattern and pattern directives will validate the regex against the viewValue of ngModel, i.e. the value of the model before the $parsers are applied.

Previously, the modelValue (the result of the $parsers) was validated and that is not really something user is inputting…

IMPORTANT! If you rely on the pattern being validated against the modelValue, you must create your own validator directive that overwrites the built-in pattern validator. See example in changelog.

24. You can now create custom xhr objects using $http by $xhrFactory

//Replace or decorate this service to create your own custom //XMLHttpRequest objects.
angular.module(‘myApp’, [])
.factory(‘$xhrFactory’, function() {
return function createXhr(method, url) {
return new window.XMLHttpRequest({mozSystem: true});
};
});

25. Animations changed — Too much to write about, more information from author

EXTRA — transclusion magic with $compile and parentBoundTranscludeFn

When you are manually compiling HTML in a directive with transcluded content, it will use wrong scope unless you use something called “parentBoundTranscludeFn”.

The story is that we have directive that uses transclusion but it also chooses what template to use based on some conditions. Because we are requesting/choosing directive template in postLink, we also use $compile to compile results.

Normally you would do something like this:

$compile(element.contents())(scope);

But this will fail because there is a transcluded content in element. To make it work we use “well documented” feature:

$compile(element.contents())(scope, undefined, {parentBoundTranscludeFn: transclude});

In fact third paramter here is “options” object — An optional object hash with linking options. If options is provided, then the following keys may be used to control linking behavior:

  • parentBoundTranscludeFn — the transclude function made available to directives; if given, it will be passed through to the link functions of directives found in element during compilation.
  • transcludeControllers — an object hash with keys that map controller names to controller instances; if given, it will make the controllers available to directives.
  • futureParentElement — defines the parent to which the cloneAttachFn will add the cloned elements; only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements). See also the directive.controller property.

More on $compile can be found in official documentation.

See you next time

That’s all for today. Again I hope you have found something interesting in this list. As always I urge you to read full AngularJS change log to always be up-to-date:

If you have not seen it I also recommend reviewing “AngularJS: Did you know…? From 1.0 to 1.3, part 1 and part 2”.