来自其他控制器的指令控制器中的调用方法


118

我有一个具有自己的控制器的指令。请参见以下代码:

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');
    }
});

所以打电话时show开启popdown指令控制器,链接功能也应该被触发,执行动画。我该如何实现?


popdown在页面上的哪里放置对指令的调用-是在一个应该让其他控制器都可以访问它的地方,还是在不同地方有几个弹出窗口?
satchmorun

我的index.html具有以下内容:<div ng-view> </ div> <div popdown> </ div>基本上只有1个popdown实例,因为它是全局可用的。
user253530

1
我想你是想写popdown.show(...)而不是popdown.notify(...)对吗?否则,notify函数有点令人困惑。
lanoxx 2014年

它来自popdown.notify哪里?.notifiy方法,我的意思是
2015年

Answers:


167

这是一个有趣的问题,我开始考虑如何实现这样的目标。

我想到了这个(提琴)

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

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

我在模块中放了两件事,一个factory是可以在任何地方注入的API,另一个是directive定义实际popdown元素的行为:

工厂仅定义了几个函数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>'
    }
})

然后定义一个app依赖于的模块Popdown

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>

我不确定这是否完全理想,但是这似乎是一种使用具有全局意义的popdown指令建立通信的合理方法。

再次,作为参考,摆弄


10
+1永远不要从指令外部调用指令中的函数-这是一种不好的做法。使用服务来管理指令读取的全局状态非常普遍,这是正确的方法。更多应用程序包括通知队列和模式对话框。
Josh David Miller

7
真是个非凡的答案!对于我们这些来自jQuery和Backbone的人来说,这样的例子非常有用
Brandon

11
这样可以使用该模块在同一视图中实例化多个指令吗?如何调用此指令的特定实例的成功或错误功能?
ira

3
@ira您可能会更改工厂以保留状态和消息对象的映射(或列表),然后在指令上使用name属性来标识所需列表中的哪个项目。因此,您无需调用success(msg)html而是可以sucess(name, msg)选择具有正确名称的指令。
lanoxx 2014年

5
@JoshDavidMiller为什么您认为在指令上调用方法是不好的做法?如果指令按预期封装了某些DOM逻辑,那么公开API当然是很自然的,以便使用该指令的控制器可以根据需要调用其方法?
保罗·泰勒

27

您还可以使用事件来触发弹出窗口。

这是一个基于satchmorun解决方案的小提琴。它省去了PopdownAPI,而顶级控制器则$broadcast在作用域链的下游发送了“成功”和“错误”事件:

$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();
});

至少这有效,在我看来,这是一个很好的分离解决方案。如果出于某种原因被认为是不好的做法,我会让其他人听到。


1
我可以想到的一个缺点是,在选定的答案中,您仅需要PopdownAPI(可通过DI轻松获得)。在这一步中,您需要访问控制器的作用域以广播消息。无论如何,它看起来非常简洁。
朱利安

我喜欢这种方法优于简单用例的服务方法,因为它可以降低复杂性并保持松散耦合
Patrick Favre 2014年

11

你也可能使该指令的控制器父范围,如ngFormname属性的作用:http://docs.angularjs.org/api/ng.directive:ngForm

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

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


这需要控制器之间的关系,对吗?由于OP需要一个消息中心,因此这对他来说可能不是理想的选择。但是也很高兴学习您的方法。在许多情况下它很有用,就像您说的那样,angular本身就使用它。
fasfsfgs

我试图遵循satchmorun提供的示例。我在运行时生成了一些html,但未使用指令的模板。我正在使用指令的控制器来指定要从添加的html调用的函数,但是未调用该函数。基本上,我有以下指令:directives.directive('abcXyz',函数($ compile {return {限制:'AE',要求:'ngModel',控制器:函数($ scope){$ scope.function1 = function() {..};},我的html是:“ <a href="" ng-click="function1('itemtype')">
标记

如果指令不是单例,这是唯一可以公开指令api的优雅解决方案!我仍然不喜欢使用$scope.$parent[alias]它,因为它对我来说就像使用内部的api。但是对于非单指令,仍然找不到更优雅的解决方案。其他变体,例如广播事件或在父控制器中为指令api气味定义更多空对象。
Ruslan Stelmachenko

3

我有更好的解决方案。

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

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();
}];

这基本上与关注点分离原则背道而驰。您向指令提供在控制器中实例化的对象,并将管理该对象(即创建调用函数)的责任委托给指令。我认为,这不是更好的解决方案。
Florin Vistig
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.