Valid directive names
-
21-12-2019 - |
Question
I've a very specific issue with a directive beginning with certain letters (t, u, v, w, y, z). The directive is used on a select
element and is watching for ngOptions
changes.
When ngOptions
is populated with data from an Ajax request, and the directive name begins with a certain letter, e.g. w, the second watch callback fires before the select options have been created. If the directive name starts with a different letter (a, k, m, n) the second watch callback fires after the options have been created.
Here is a sample with multiple directives starting with various letters. Open your console to see the problem.
<select w-foo ng-model="bar" ng-options="o.name for o in options"></select>
app.controller("test", function ($scope, $timeout) {
// Emulate ajaxed options
$timeout(function () {
$scope.options = [{
name: "aaa",
id: 1
}, {
name: "bbb",
id: 2
}, {
name: "ccc",
id: 3
}];
}, 500);
});
app.directive("wFoo", function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
scope.$watch(attrs.ngOptions.replace(/.*in /, ""), function () {
console.log("w-foo ngOptions changed", element.html());
});
}
};
});
In the above code, element.html()
will contain a single empty option
in the first watch callback (the initial digest) and will also contain a single empty option
in the second digest (when the Ajax is complete).
Where does this behaviour come from?
Solution
There is three mechanisms here which are involved to explains this behaviour:
The watchers are executed in the same order that they are registered (see the code of the
$watch
method()).The post-link functions are executed in the reverse order of their priority (see the documentation for the
priority
property).Directives with the same priority are executed in an undefined order. The default priority is
0
, so all your custom directives here have this priority. Since theselect
directive as also a priority of0
, the order of execution is undefined.As you notice, "undefined" is a bit excessive, and actually the order of execution is the alphabetical order (see the
$compile
code): directives with a name in the beginning of the dictionary have a higher priority. But, in my humble opinion, since this isn't explicitly said in the documentation, you can't rely on this behaviour.
To summarize, here is some use cases. Each directive registers a watcher.
Name | Priority | Watcher order
--------+------------+--------------
A | 100 | 3rd
B | 0 | 2nd
C | none (= 0) | 1st
Thus, to fix the problem is simple:
- If you want to execute the watcher of your custom directives after the
select
one, just give to your directive a priority higher than0
. - If you want to execute the watcher of your custom directives before the
select
one, well… since negative priority are forbidden, you can't reliably do that.