如何确保指令的链接功能在控制器之前运行?

时间:2014-03-27 20:06:04

标签: javascript angularjs controller angularjs-directive angularjs-scope

在我们的应用程序中,我们通过简单的路径加载视图。

$routeProvider
    .when('/', {
        template: require('./views/main.tpl.html'),
        controller: 'mainCtrl'
    })
    .otherwise({
        redirectTo: '/'
    });

这会将mainCtrl控制器与视图相关联。视图如下所示:

<left-rail>
    <filter-sub-rail
        id="filter-rail"
        show-all-filter="isAdmin"
        components="components">
    </filter-sub-rail>
</left-rail>

<div class="endor-Page-content endor-Panel">
    <action-bar
        selected-components="selectedComponents"
        toggleable-columns="toggleableColumns"
        refresh-component-list="refreshComponentList()">
    </action-bar>
    <filter-pills></filter-pills>
    <div class="js-endor-content endor-Panel-content endor-Panel-content--actionBarHeight">
        <component-table
            components="components"
            selected-components="selectedComponents"
            toggleable-columns="toggleableColumns">
        </component-table>
    </div>
    <spinner id="cmSpinner" large="true" center="true"></spinner>
    <modal></modal>
</div>

第三行到最后一行包含<spinner>指令。该指令在加载数据时创建一个微调器图形。它有一个名为spinnerApi的关联Angular服务。 spinner指令使用spinner API注册微调器,允许其他服务注入微调器API并调用showhide方法,传入ID,以显示/隐藏所需的微调器。

mainCtrl中,有一个函数在控制器运行的那一刻开始加载一些数据。

//spinnerApi.show('spinner1');
var data = Segment.query({
    /*...truncated for brevity...*/
}, function () {
    $scope.components = data;
    spinnerApi.hide('spinner1');
});

您可以看到对spinnerApi.show的第一次通话已被注释掉。那是因为此时spinner指令没有运行其链接函数,并且微调器尚未在API中注册。如果我取消注释该行,我会得到一个异常,因为还没有名为spinner1的微调器。但是,当回调从查询运行时,微调器可用并且调用成功。

如何在开始在控制器中加载数据之前确保spinner指令运行并使用API​​ 注册微调器并避免此竞争条件?

2 个答案:

答案 0 :(得分:3)

以另一种方式思考:

让控制器在范围内发布一个标志,意味着api正在加载。将此标志传递给模板中的指令,并观察它是否在指令链接函数中切换微调器。首选prelink函数到postlink函数,用于元素显示(DOM渲染优化)。在观察者之外的预链接时间设置微调器可见性!否则它会等待第一个摘要循环发生,以隐藏你已经支付了显示一次的成本的微调器!

如果有多个行为会影响此显示标志,请创建一个服务来保存此标志,在控制器的作用域上发布此服务,并将该标志作为模板中指令的输入。在指令中注入此服务意味着您的微调器指令与其可见性条件之间的耦合过多。


来自问题作者编辑:

这个答案给了我一个很好的想法,看起来很小。我不希望控制器或指令执行任何奇怪的等待逻辑,因此有意义的是,spinnerApi服务中应该发生某些事情(该指令应该可以在应用程序的任何地方重复使用)。

这是我的spinnerApi服务,如果spinnerId尚未注册,则修改为队列隐藏/显示/切换事件。当register方法运行时,它会在队列中查找是否有任何操作。

module.exports = angular.module('shared.services.spinner-api', [])
    // Simple API for easy spinner control.
    .factory('spinnerApi', function () {
        var spinnerCache = {};
        var queue = {};
        return {
            // All spinners are stored here.
            // Ex: { spinnerId: isolateScope }
            spinnerCache: spinnerCache,

            // Registers a spinner with the spinner API.
            // This method is only ever really used by the directive itself, but
            // the API could be used elsewhere if necessary.
            register: function (spinnerId, spinnerData) {

                // Add the spinner to the collection.
                this.spinnerCache[spinnerId] = spinnerData;

                // Increase the spinner count.
                this.count++;

                // Check if spinnerId was in the queue, if so then fire the
                // queued function.
                if (queue[spinnerId]) {
                    this[queue[spinnerId]](spinnerId);
                    delete queue[spinnerId];
                }

            },

            // Removes a spinner from the collection.
            unregister: function (spinnerId) {
                if (!this.spinnerCache[spinnerId]) throw new Error('Spinner "' + spinnerId + '" does not exist.');
                delete this.spinnerCache[spinnerId];
            },

            // Show a spinner with the specified spinnerId.
            show: function (spinnerId) {
                if (!this.spinnerCache[spinnerId]) {
                    queue[spinnerId] = 'show';
                    return;
                }
                this.spinnerCache[spinnerId].visible = true;
            },

            // Hide a spinner with the specified spinnerId.
            hide: function (spinnerId) {
                if (!this.spinnerCache[spinnerId]) {
                    queue[spinnerId] = 'hide';
                    return;
                }
                this.spinnerCache[spinnerId].visible = false;
            },

            // Hide/show a spinner with the specified spinnerId.
            toggle: function (spinnerId) {
                if (!this.spinnerCache[spinnerId]) {
                    queue[spinnerId] = 'toggle';
                    return;
                }
                this.spinnerCache[spinnerId].visible = !this.spinnerCache[spinnerId].visible;
            },

            // Show all spinners tracked by the API.
            showAll: function () {
                for (var key in this.spinnerCache) {
                    this.show(key);
                }
            },

            // Hide all spinners tracked by the API.
            hideAll: function () {
                for (var key in this.spinnerCache) {
                    this.hide(key);
                }
            },

            // Hide/show all spinners tracked by the API.
            toggleAll: function () {
                for (var key in this.spinnerCache)
                    this.spinnerCache[key].visible = !this.spinnerCache[key].visible;
            },

            // The number of spinners currently tracked by the API.
            count: 0
        };
    });

答案 1 :(得分:1)

compile会在controller之前运行,link会在link功能之前运行。因此,无论您在compile声明中做什么(更改元素,添加属性等)都应该在module.exports = angular.module('shared.directives.spinner', [ require('services/spinner-api').name ]).directive('spinner', function (spinnerApi){ return { restrict: 'AE', template: '<div ng-show="visible" class="coral-Wait" ng-class="{ \'coral-Wait--center\': center, \'coral-Wait--large\': large }"></div>', replace : true, scope : {}, priority: 100, compile : function (){ return { pre: function (scope, element, attrs, controller){ // Get the spinner ID, either manually specified or var spinnerId = typeof(attrs.id) !== 'undefined' ? attrs.id : spinnerApi.count; // Set isolate scope variables. // scope.visible = !!attrs.visible || false; scope.center = !!attrs.center || false; scope.large = !!attrs.large || false; // Add the spinner to the spinner API. // The API stores a simple hash with the spinnerId as // the key and that spinner's isolate scope as the value. spinnerApi.register(spinnerId, scope); } }; } }; }); app.factory('spinnerApi', function(){ var spinnerApi; spinnerApi.repo = {}; spinnerApi.show = function(name){ if (!spinnerApi.repo[name]) { spinnerApi.repo[name] = {show: false}; } spinnerApi.repo[name].show = true; } spinnerApi.register = function(id, scope){ if (!spinnerApi.repo[name]){ spinnerApi.repo[name] = {show: false}; } spinnerApi.repo[name].scope = scope; scope.$watch(function(){ return spinnerApi.repo[name].show; }, function(newval, oldval){ if (newval){ spinnerApi.repo[name].scope.visible = newval; } }); }; /* "pseudo" code */ return spinnerApi; }); 阶段完成。

由于您使用的是在指令和控制器之间共享状态的服务,因此您可以随意添加和删除微调器,而不管它们的调用顺序如何。这意味着,从您的代码中获取,而不是注册微调器,当您实际到达指令中的链接时,您将改变它的状态:

$watch

虽然我认为您的服务不应该知道您的范围。服务应该保持“原始状态”,您的指令应该采取行动。这意味着,{{1}}应该真正进入你的指令中的孤立范围内(如果有的话)。