如何从指令部分调用ng-click?

3
我有一个指令,其中包含一个局部范围,其中包含ng-click的partial。
这里是Fiddle: http://jsfiddle.net/stephanedeluca/QRZFs/13/ 不幸的是,自从我把代码移到指令中后,ng-click就不再起作用了。
控制器和指令如下:
var app = angular.module('myApp', ['ngSanitize']);

app.directive('plantStages', function ($compile) {
    return {
        restrict: 'E',
        transclude: true,
        template: '<figure class="cornStages">\
                        <p ng-transclude style="color: skyblue"></p>\
                        <hr/>\
                        <p ng-bind-html="title"></p>\
                        <p ng-bind-html="subtitle">{{subtitle}}</p>\
                        <ul>\
                            <li ng-repeat="stage in stages" ng-click="changePage(stage)">{{stage}}</li>\
                        </ul>\
                    </figure>',
        scope: {
            stages:"=",
            title:'@'
        },
        link: function (scope, element, attrs, ctrl, transclude) {
            if (!attrs.title) scope.title = "Default title";
        }
    };
});

app.controller('myCtrl', function ($scope, $location, $http) {
    $scope.stages = ['floraison', 'montaison'];
    $scope.changePage = function (page) {
        var url = "corn.page.html#/"+page;
        console.log("Change page "+page+" with url "+url);
        alert("about to change page as follows: document.location.href = "+url);
    };

});

调用它的HTML代码如下:

<div ng-controller="myCtrl">
    Stages, 
    <p ng-repeat="stage in stages">{{stage}}</p>
    <hr/>
    Plant stages
    <plant-stages 
        title="<b>Exploration<br/>du cycle</b>"
        subtitle="<em>This is a<br/>sub title</em>"
        stages="stages"
    >
        Inner<br/>directive
    </plant-stages>
</div>

有什么想法吗?

1
你好,请查看这个答案:https://dev59.com/1mQn5IYBdhLWcg3w5aez - mpm
2个回答

7
您不能直接从指令中访问控制器作用域中定义的changePage(),因为您的指令具有隔离作用域。但是,仍然有几种方法可以实现:

选项1:

选项1是最简单的选项。但是它更像是一个变通方法,我不建议广泛使用它。您可以从传递给链接函数的元素中获取控制器的作用域,并在那里调用changePage

link: function (scope, element, attrs, ctrl, transclude) {
    if (!attrs.title) scope.title = "Default title";
    scope.changePage = element.scope().changePage; // <= Get parent scope from element, it will have changePage()
}

选项2:

如果你没有任何涉及外部控制器范围的逻辑(如你的示例),你可以为指令定义内部控制器并在那里执行:

app.directive('plantStages', function ($compile) {
    return {
       ...
       controller: ['$scope', function($scope) {
           $scope.changePage = function(page) {
               var url = "corn.page.html#/"+page;
               console.log("Change page "+page+" with url "+url);
               alert("about to change page as follows: document.location.href = "+url);
           }
       }]
    };
});

选项3:

如果您想在不同的指令和控制器中重用changePage()中定义的逻辑,最好的方法是将逻辑移动到某个服务中,该服务可以注入到控制器和指令中:

app.service('changePageService', function() {
    this.changePage = function(page) {
        var url = "corn.page.html#/"+page;
        console.log("Change page "+page+" with url "+url);
        alert("about to change page as follows: document.location.href = "+url);
    }
});

app.controller('myCtrl', function ($scope, $location, $http, changePageService) {
    ...
    changePageService.changePage('page');
    ...
});

app.directive('plantStages', function ($compile) {
    ...
    controller: ['$scope', 'changePageService', function($scope, changePageService) {
        $scope.changePage = changePageService.changePage;
    }]
    ...
});

选项4:

您可以将像changePage(page)这样的代码片段作为指令的某个属性的值传递,并在指令内部定义带有'&'的作用域属性,该属性将创建一个函数,在外部控制器范围内执行传递给该函数的参数。例如:

JavaScript

app.directive('plantStages', function ($compile) {
    return {
        restrict: 'E',
        transclude: true,
        template: '<figure class="cornStages">\
                        <p ng-transclude style="color: skyblue"></p>\
                        <hr/>\
                        <p ng-bind-html="title"></p>\
                        <p ng-bind-html="subtitle"></p>\
                        <ul>\
                            <li ng-repeat="stage in stages" ng-click="changePage({page: stage})">{{stage}}</li>\
                        </ul>\
                    </figure>',
        scope: {
            stages:"=",
            title:'@',
            changePage:'&'
        },
        link: function (scope, element, attrs, ctrl, transclude) {
            if (!attrs.title) scope.title = "Default title";
        }
    };
});

HTML

<div ng-controller="myCtrl">
    Stages, 
    <p ng-repeat="stage in stages">{{stage}}</p>
    <hr/>
    Plant stages
    <plant-stages 
        title="<b>Exploration<br/>du cycle</b>"
        subtitle="<em>This is a<br/>sub title</em>"
        stages="stages"
        change-page="changePage(page)"
    >
        Inner<br/>directive
    </plant-stages>

Plunker: http://plnkr.co/edit/s4CFI3wxs0SOmZVhUkC4?p=preview 是一个在线的代码编辑器,可以帮助开发人员快速创建、共享和调试 Web 应用程序。它支持 HTML、CSS、JavaScript 和其他流行的 Web 技术,并提供实时预览功能,方便用户查看他们所编写的代码的效果。

哇,非常有用且详尽的回答,你让我一天都很开心。不过有一个问题,(我对所有3个选项进行了测试,并保留了#2)页面切换在文档加载时就已经触发了。如何防止这种情况?(我的fiddle链接http://jsfiddle.net/stephanedeluca/QRZFs/27/) - Stéphane de Luca
1
@StéphanedeLuca 在你的fiddle中,你还将changePageService.changePage('page');粘贴到了我添加的myCtrl控制器中,作为如何在控制器内重用和使用changePageService服务的示例。你应该删除或注释此行代码,这样在文档加载时就不会进行页面切换。http://jsfiddle.net/L8VbK/2/ - Vadim
哎呀,你说得对(这就解释了为什么我在我的应用程序中没有遇到这个问题!)非常感谢,Vadim。 - Stéphane de Luca

1
指令的理念是将其视为可重用的组件,并尽可能避免外部依赖。如果您有可能在自己的控制器中定义指令的行为,则应该这样做。
module.directive('myDirective', function () {
  return {
    restrict: 'E',
    controller: function() { /* behaviour here */ },
    template: '<div>Directive Template</div>',
        scope: {
            /* directive scope */
        }
    };
});

如果不可能的话,你可以像链接问题中所解释的那样传递函数(参见上面的评论)。请检查更新的小测试

这正是让我感到不舒服的地方,如果我必须传递函数(甚至是名称)。对我来说,这是一种架构上的弱点 :-/ - Stéphane de Luca
1
你说得对。这使得指令更难以重用。最好将行为(在您的情况下是 changePage())封装在指令的控制器中 http://jsfiddle.net/N6P6T/2/ - Sebastian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接