【问题标题】:Call method in directive controller from other controller从其他控制器调用指令控制器中的方法
【发布时间】:2013-01-30 18:22:15
【问题描述】:

我有一个指令,它有自己的控制器。见以下代码:

var popdown = angular.module('xModules',[]);

popdown.directive('popdown', function () {
    var PopdownController = function ($scope) {
        this.scope = $scope;
    }

    PopdownController.prototype = {
        show:function (message, type) {
            this.scope.message = message;
            this.scope.type = type;
        },

        hide:function () {
            this.scope.message = '';
            this.scope.type = '';
        }
    }

    var linkFn = function (scope, lElement, attrs, controller) {

    };

    return {
        controller: PopdownController,
        link: linkFn,
        replace: true,
        templateUrl: './partials/modules/popdown.html'
    }

});

这是一个用于错误/通知/警告的通知系统。我想要做的是从另一个控制器(不是指令控制器)调用此控制器上的函数show。当我这样做时,我还希望我的链接函数能够检测到某些属性发生了变化并执行一些动画。

这里有一些代码来举例说明我的要求:

var app = angular.module('app', ['RestService']);

app.controller('IndexController', function($scope, RestService) {
    var result = RestService.query();

    if(result.error) {
        popdown.notify(error.message, 'error');
    }
});

所以当在popdown 指令控制器上调用show 时,链接函数也应该被触发并执行动画。我怎样才能做到这一点?

【问题讨论】:

  • 你在哪里调用页面上的 popdown 指令 - 它只是在一个其他控制器都应该可以访问它的地方,还是在不同的地方有几个弹出窗口?
  • 我的 index.html 有这个:
    基本上只有 1 个 popdown 实例,因为它意味着全球可用。
    跨度>
  • 我想你的意思是写popdown.show(...)而不是popdown.notify(...),对吗?否则通知功能有点混乱。
  • 它来自哪里popdown.notify.notifiy 方法,我的意思是
  • 标签: javascript jquery angularjs angularjs-directive


    【解决方案1】:

    我得到了更好的解决方案。

    这是我的指令,我在指令中注入了对象引用,并通过在指令代码中添加调用函数来扩展它。

    app.directive('myDirective', function () {
        return {
            restrict: 'E',
            scope: {
            /*The object that passed from the cntroller*/
            objectToInject: '=',
            },
            templateUrl: 'templates/myTemplate.html',
    
            link: function ($scope, element, attrs) {
                /*This method will be called whet the 'objectToInject' value is changes*/
                $scope.$watch('objectToInject', function (value) {
                    /*Checking if the given value is not undefined*/
                    if(value){
                    $scope.Obj = value;
                        /*Injecting the Method*/
                        $scope.Obj.invoke = function(){
                            //Do something
                        }
                    }    
                });
            }
        };
    });
    

    在 HTML 中用参数声明指令:

    <my-directive object-to-inject="injectedObject"></ my-directive>
    

    我的控制器:

    app.controller("myController", ['$scope', function ($scope) {
       // object must be empty initialize,so it can be appended
        $scope.injectedObject = {};
    
        // now i can directly calling invoke function from here 
         $scope.injectedObject.invoke();
    }];
    

    【讨论】:

    • 这基本上违背了关注点分离原则。您向指令提供在控制器中实例化的对象,并将管理该对象(即创建调用函数)的责任委托给指令。在我看来,这不是更好的解决方案。
    【解决方案2】:

    您还可以将指令的控制器公开给父作用域,例如带有name 属性的ngFormhttp://docs.angularjs.org/api/ng.directive:ngForm

    在这里您可以找到一个非常基本的示例如何实现它http://plnkr.co/edit/Ps8OXrfpnePFvvdFgYJf?p=preview

    在这个例子中,我有 myDirective 和带有 $clear 方法的专用控制器(指令的一种非常简单的公共 API)。我可以将此控制器发布到父作用域并在指令之外使用调用此方法。

    【讨论】:

    • 这需要控制器之间的关系,对吧?由于 OP 想要一个消息中心,这对他来说可能并不理想。但是学习你的方法也很好。它在许多情况下都很有用,就像你说的那样,Angular 本身就使用它。
    • 我试图遵循 satchmorun 提供的示例。我在运行时生成了一些 html,但我没有使用指令的模板。我正在使用指令的控制器来指定要从添加的 html 调用的函数,但该函数没有被调用。基本上,我有这个指令:directives.directive('abcXyz', function ($compile { return { restrict: 'AE', require: 'ngModel', controller: function ($scope) { $scope.function1 = function () { .. }; },我的 html 是:"
    • 如果指令不是单例,这是唯一可以公开指令 api 的优雅解决方案!我仍然不喜欢使用$scope.$parent[alias],因为它对我来说就像使用内部角度 api 一样。但仍然无法为非单例指令找到更优雅的解决方案。其他变体,例如广播事件或在父控制器中定义空对象以获取指令 api 气味更甚。
    【解决方案3】:

    您也可以使用事件来触发 Popdown。

    Here's a fiddle 基于 satchmorun 的解决方案。它省去了 PopdownAPI,而是使用顶级控制器$broadcasts 'success' 和 'error' 事件沿着作用域链向下:

    $scope.success = function(msg) { $scope.$broadcast('success', msg); };
    $scope.error   = function(msg) { $scope.$broadcast('error', msg); };
    

    Popdown 模块然后为这些事件注册处理函数,例如:

    $scope.$on('success', function(event, msg) {
        $scope.status = 'success';
        $scope.message = msg;
        $scope.toggleDisplay();
    });
    

    这至少可行,而且在我看来是一个很好的解耦解决方案。如果由于某种原因这被认为是不好的做法,我会让其他人加入。

    【讨论】:

    • 我能想到的一个缺点是,在选定的答案中,您只需要 PopdownAPI(很容易通过 DI 获得)。在这个中,您需要访问控制器的范围来广播消息。反正看起来很简洁。
    • 我更喜欢这种简单用例的服务方法,因为它降低了复杂性并且仍然是松散耦合的
    【解决方案4】:

    这是一个有趣的问题,我开始思考如何实现这样的事情。

    我想出了this (fiddle)

    基本上,我没有尝试从控制器调用指令,而是创建了一个模块来容纳所有弹出逻辑:

    var PopdownModule = angular.module('Popdown', []);
    

    我在模块中放了两件事,factory 用于可以在任何地方注入的 API,directive 用于定义实际弹出元素的行为:

    工厂只定义了几个函数 successerror 并跟踪几个变量:

    PopdownModule.factory('PopdownAPI', function() {
        return {
            status: null,
            message: null,
            success: function(msg) {
                this.status = 'success';
                this.message = msg;
            },
            error: function(msg) {
                this.status = 'error';
                this.message = msg;
            },
            clear: function() {
                this.status = null;
                this.message = null;
            }
        }
    });
    

    该指令将 API 注入其控制器,并监视 api 的变化(为了方便,我使用 bootstrap css):

    PopdownModule.directive('popdown', function() {
        return {
            restrict: 'E',
            scope: {},
            replace: true,
            controller: function($scope, PopdownAPI) {
                $scope.show = false;
                $scope.api = PopdownAPI;
    
                $scope.$watch('api.status', toggledisplay)
                $scope.$watch('api.message', toggledisplay)
    
                $scope.hide = function() {
                    $scope.show = false;
                    $scope.api.clear();
                };
    
                function toggledisplay() {
                    $scope.show = !!($scope.api.status && $scope.api.message);               
                }
            },
            template: '<div class="alert alert-{{api.status}}" ng-show="show">' +
                      '  <button type="button" class="close" ng-click="hide()">&times;</button>' +
                      '  {{api.message}}' +
                      '</div>'
        }
    })
    

    然后我定义一个依赖Popdownapp模块:

    var app = angular.module('app', ['Popdown']);
    
    app.controller('main', function($scope, PopdownAPI) {
        $scope.success = function(msg) { PopdownAPI.success(msg); }
        $scope.error   = function(msg) { PopdownAPI.error(msg); }
    });
    

    HTML 看起来像:

    <html ng-app="app">
        <body ng-controller="main">
            <popdown></popdown>
            <a class="btn" ng-click="success('I am a success!')">Succeed</a>
            <a class="btn" ng-click="error('Alas, I am a failure!')">Fail</a>
        </body>
    </html>
    

    我不确定它是否完全理想,但它似乎是一种使用全局弹出指令建立通信的合理方式。

    再次作为参考,the fiddle

    【讨论】:

    • +1 永远不应该从指令外部调用指令中的函数——这是一种不好的做法。使用服务来管理指令读取的全局状态非常普遍,这是正确的方法。更多应用包括通知队列和模式对话框。
    • 非常出色的答案!对于我们这些来自 jQuery 和 Backbone 的人来说,这样一个有用的例子
    • 这样可以用这个模块在同一个视图中实例化多个指令吗?如何调用该指令的特定实例的成功或错误函数?
    • @ira 您可能会更改工厂以保留状态和消息对象的映射(或列表),然后使用指令上的名称属性来识别您需要列表中的哪个项目。因此,与其在 html 中调用 success(msg),不如调用 sucess(name, msg) 来选择具有正确名称的指令。
    • @JoshDavidMiller 为什么你认为在指令上调用方法是不好的做法?如果一个指令按预期封装了一些 DOM 逻辑,那么公开一个 API 以便使用它的控制器可以根据需要调用它的方法是很自然的吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多