【问题标题】:How to properly bind scope between directive and controller with angularJS如何使用angularJS正确绑定指令和控制器之间的范围
【发布时间】:2013-03-01 14:25:10
【问题描述】:

我正在尝试使用 anugularJS 生成一个 n 级分层无序列表,并且已经能够成功地做到这一点。但是现在,我在指令和控制器之间遇到了范围问题。我需要在指令模板中通过 ng-click 调用的函数中更改父级的范围属性。

http://jsfiddle.net/ahonaker/ADukg/2046/ - 这里是 JS

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

//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});

function MyCtrl($scope) {
    $scope.itemselected = "None";
    $scope.organizations = {
        "_id": "SEC Power Generation",
        "Entity": "OPUNITS",
        "EntityIDAttribute": "OPUNIT_SEQ_ID",
        "EntityID": 2,
        "descendants": ["Eastern Conf Business Unit", "Western Conf Business Unit", "Atlanta", "Sewanee"],
        children: [{
            "_id": "Eastern Conf Business Unit",
            "Entity": "",
            "EntityIDAttribute": "",
            "EntityID": null,
            "parent": "SEC Power Generation",
            "descendants": ["Lexington", "Columbia", "Knoxville", "Nashville"],
            children: [{
                "_id": "Lexington",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 10,
                "parent": "Eastern Conf Business Unit"
            }, {
                "_id": "Columbia",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 12,
                "parent": "Eastern Conf Business Unit"
            }, {
                "_id": "Knoxville",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 14,
                "parent": "Eastern Conf Business Unit"
            }, {
                "_id": "Nashville",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 4,
                "parent": "Eastern Conf Business Unit"
            }]
        }]
    };

    $scope.itemSelect = function (ID) {
        $scope.itemselected = ID;
    }
}

app.directive('navtree', function () {
    return {
        template: '<ul><navtree-node ng-repeat="item in items" item="item" itemselected="itemselected"></navtree-node></ul>',
        restrict: 'E',
        replace: true,
        scope: {
            items: '='
        }
    };
});

app.directive('navtreeNode', function ($compile) {
    return {
        restrict: 'E',
        template: '<li><a ng-click="itemSelect(item._id)">{{item._id}} - {{itemselected}}</a></li>',
        scope: {
            item: "=",
            itemselected: '='
        },
        controller: 'MyCtrl',
        link: function (scope, elm, attrs) {
            if ((angular.isDefined(scope.item.children)) && (scope.item.children.length > 0)) {
                var children = $compile('<navtree items="item.children"></navtree>')(scope);
                elm.append(children);
            }
        }
    };
});

这是 HTML

<div ng-controller="MyCtrl">
    Selected: {{itemselected}}

    <navtree items="organizations.children"></navtree>
</div>

请注意,列表是从模型生成的。而ng-click调用该函数设置父作用域属性(itemselected),但改变只发生在本地。当我单击一个项目时,预期的行为是“已选择:无”应更改为“已选择:xxx”,其中 xxx 是单击的项目。

我没有适当地绑定父作用域和指令之间的属性吗?如何将属性更改传递给父范围?

希望这很清楚。

提前感谢您的帮助。

【问题讨论】:

  • 欢迎来到 StackOverflow!如果您以接近结尾的实际问题的形式重新表述它,它可能会改善您的帖子。

标签: scope angularjs hierarchy using-directives


【解决方案1】:

这可能是因为每个指令都创建了自己的范围(实际上是您告诉他们这样做的)。
您可以阅读有关指令here 的更多信息,尤其是“编写指令(长版)”一章。

范围 - 如果设置为:

true - 然后将为该指令创建一个新范围。如果 同一元素上的多个指令请求一个新的范围,只有一个 新范围被创建。新的范围规则不适用于根 模板的根,因为模板的根总是得到一个新的 范围。

{}(对象哈希) - 然后创建一个新的“隔离”范围。这 “隔离”范围与正常范围不同,因为它不 原型继承自父作用域。这在以下情况下很有用 创建可重用的组件,不应意外读取或 修改父范围内的数据。

因此您所做的更改不会反映在 MyCtrl 范围内,因为每个指令都有自己的“隔离”范围。

这就是为什么单击只会更改本地 $scope.itemselected 变量而不是“全部”变量。

【讨论】:

  • 感谢 maxdec,但该指令需要自己的范围才能递归遍历层次结构。如何获得两全其美:层次结构的本地范围和对其他属性的父范围的访问?我已经阅读了您链接到令人作呕的文档,并认为我理解了,特别是关于“= 或 =attr - 在本地范围属性和父范围属性之间设置双向绑定......”和“任何更改到 parentModel 将反映在 localModel 中,localModel 中的任何更改都将反映在 parentModel 中。"但我就是做不到。
  • @user2165994, "我怎样才能两全其美:层次结构的本地范围和对其他属性的父范围的访问?" -- 使用scope: true。您的指令将从父范围获得 prototypically inherits 自己的范围。因此,您可以定义自己的属性和/或引用父属性。如果您需要写入父属性,请确保它是对象属性(例如,model.prop1),而不是原始属性(prop1)。
【解决方案2】:

Maxdec 是对的,这与作用域有关。不幸的是,这种情况非常复杂,以至于 AngularJS 文档可能会误导初学者(比如我自己)。

警告:请做好准备,因为我试图解释这一点时有点啰嗦。如果您只想查看代码,请转到此JSFiddle。我还发现 egghead.io 视频对于了解 AngularJS 非常有用。

这是我对这个问题的理解:您有一个层次结构的指令(navtree、navitem),并且您希望将信息从 navitem “向上”传递到根控制器。 AngularJS 与一般编写良好的 Javascript 一样,对变量的范围进行了严格设置,这样您就不会意外地弄乱页面上也在运行的其他脚本。

Angular 中有一个特殊的语法 (&amp;),它可以让你创建一个隔离作用域并在父作用域上调用一个函数:

// in your directive
scope: {
   parentFunc: '&'
}

到目前为止一切顺利。当您有多个级别的指令时,事情会变得很棘手,因为您基本上想要执行以下操作:

  1. 在根控制器中有一个函数可以接受变量并更新模型
  2. 中级指令
  3. 可以与根控制器通信的子级指令

问题是,子级指令看不到根控制器。我的理解是,您必须在指令结构中设置一个“链”,其作用如下:

首先:在你的根控制器中有一个函数,它返回一个函数(它引用了根视图控制器的作用域):

$scope.selectFunctionRoot = function () {
    return function (ID) {
        $scope.itemselected = ID;
    }
}

第二: 设置中级指令,使其拥有自己的选择函数(它将传递给子级),该函数返回如下内容。注意我们必须如何保存中级指令的范围,因为当这段代码实际执行时,它将在子级指令的上下文中:

// in the link function of the mid-level directive. the 'navtreelist'
scope.selectFunctionMid = function () {
    // if we don't capture our mid-level scope, then when we call the function in the navtreeNode it won't be able to find the mid-level-scope's functions            
    _scope = scope;
    return function (item_id) {
        console.log('mid');
        console.log(item_id);

        // this will be the "root" select function
        parentSelectFunction = _scope.selectFunction();
        parentSelectFunction(item_id);
    };
};

第三: 在子级指令 (navtreeNode) 中,将一个函数绑定到调用本地函数的 ng-click,这反过来将“调用链”所有通往根控制器的方式:

// in 'navtreeNode' link function
scope.childSelect = function (item_id) {
    console.log('child');
    console.log(item_id);

    // this will be the "mid" select function  
    parentSelectFunction = scope.selectFunction();
    parentSelectFunction(item_id);
};

这是更新后的fork of your JSFiddle,代码中有 cmets。

【讨论】:

  • 在控制器和指令之间使用范围的双向绑定共享通用功能的优秀答案。谢谢!
【解决方案3】:

请看看这个工作小提琴,http://jsfiddle.net/eeuSv/

我所做的是在 navtree-node 指令中要求父控制器,并调用该控制器中定义的成员函数。 成员函数是setSelected。请注意它的this.setSelected 而不是$scope.setSelected。 然后定义一个navtree-node范围方法itemSelect。当您单击锚标记时,它将在 navtree-node 范围内调用 itemSelect 方法。这反过来将调用控制器成员方法setSelected 传递选定的 id。

scope.itemSelect = function(id){ myGreatParentControler.setSelected(id) }

【讨论】:

  • 谢谢 rajkamal....我已经成功了。我仍然对require: "^ngController" ngController 来自哪里感到困惑?
  • 笼统地说,require: "^ngController" 语句查找定义了控制器的父元素。由于我们在navtree 上方定义了控制器MyCtrl,因此将其作为第四个参数传递给nav-node 指令的链接函数。您可以在指令定义对象标题下的docs.angularjs.org/guide/directive 中阅读有关此内容的信息。他们在这里定义了选项。
  • +1。好的。我还没有看到其他人给出使用 require: '^ngController' 的 SO 示例。我见过的所有其他示例都需要 ngModel 或其他指令的控制器。
  • +1 我希望我早点找到这个。能够使用 '^ngController' 将指令绑定到控制器真是太棒了。非常感谢@rajkamal!
  • @rajkamal 请将您的答案发布到您的答案中,而不仅仅是通过 JSFiddle。如果 JSFiddle 出现故障,您的答案应该保持其相关性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-11
  • 2013-05-28
  • 2016-03-28
  • 2018-08-30
相关资源
最近更新 更多