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?

Was it helpful?

Solution

There is three mechanisms here which are involved to explains this behaviour:

  1. The watchers are executed in the same order that they are registered (see the code of the $watch method()).

  2. The post-link functions are executed in the reverse order of their priority (see the documentation for the priority property).

  3. 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 the select directive as also a priority of 0, 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 than 0.
  • 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.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top