如何使用 angularJS 正确绑定指令和控制器之间的范围

How to properly bind scope between directive and controller with angularJS

提问人:user2165994 提问时间:3/13/2013 最后编辑:user2165994 更新时间:3/14/2013 访问量:40072

问:

我正在尝试使用 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 是单击的项目。

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

希望这是清楚的。

提前感谢您的任何帮助。

作用域 angularjs 层次结构 using-directives

评论

1赞 Sid Holland 3/13/2013
欢迎来到 StackOverflow!如果您在接近末尾时以实际问题的形式重新表述它,它可能会改善您的帖子。

答:

3赞 maxdec 3/14/2013 #1

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

scope - 如果设置为:

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

{} (object hash) - 然后创建一个新的“隔离”范围。这 “隔离”范围与普通范围的不同之处在于,它没有 原型继承自父作用域。这在以下情况下很有用 创建可重用的组件,这些组件不应意外读取或 修改父作用域中的数据。

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

这就是为什么点击只会改变局部变量,而不是“全部”变量。$scope.itemselected

评论

0赞 user2165994 3/14/2013
感谢 maxdec,但该指令需要自己的作用域才能递归遍历层次结构。如何获得两全其美的优势:层次结构的本地范围和对其他属性的父范围的访问?我已经阅读了您链接到的文档,并认为我理解了,尤其是关于“= 或 =attr - 在本地范围属性和父范围属性之间设置双向绑定......”的部分。& “对 parentModel 的任何更改都将反映在 localModel 中,localModel 中的任何更改都将反映在 parentModel 中。”但我就是做不到这个。
0赞 Mark Rajcok 3/14/2013
@user2165994,“如何获得两全其美的优势:层次结构的本地作用域和对其他属性的父作用域的访问?指令将获得它自己的作用域,该作用域通常继承自父作用域。因此,您可以定义自己的属性和/或引用父属性。如果需要写入父属性,请确保它是对象属性(例如,),而不是基元()。scope: truemodel.prop1prop1
11赞 Ben Jacobs 3/14/2013 #2

Maxdec 是对的,这与范围界定有关。不幸的是,这是一个足够复杂的情况,以至于 AngularJS 文档可能会误导初学者(比如我自己)。

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

这是我对这个问题的理解:你有一个指令层次结构(navtree、navitem),你想把信息从导航项“树上”传递到根控制器。AngularJS 与一般编写良好的 Javascript 一样,被设置为严格控制变量的范围,这样您就不会意外地弄乱页面上运行的其他脚本。

Angular 中有一个特殊的语法 (),它允许你创建一个隔离的范围,并在父范围上调用一个函数:&

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

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

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

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

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

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

第二:将 mid-level 指令设置为具有自己的 select 函数(它将传递给子函数),该函数返回如下内容。请注意,我们必须保存中级指令的作用域,因为当实际执行此代码时,它将在子级指令的上下文中:

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

第三:在子级指令 () 中,将一个函数绑定到调用本地函数的函数,该函数又会一直“调用链”到根控制器:navtreeNodeng-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);
};

下面是 JSFiddle 的更新分支,其中代码中有注释。

评论

0赞 Alex 4/16/2014
关于在控制器和指令之间使用作用域的双向绑定共享公共功能的出色答案。谢谢!
18赞 Rajkamal Subramanian 3/14/2013 #3

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

我所做的是要求指令中的父控制器,并调用该控制器中定义的成员函数。 成员函数为 。请注意,它不是 . 然后定义一个 scope 方法。单击锚标记时,它将调用作用域上的方法。这反过来将调用传递所选 id 的 controllers 成员方法。navtree-nodesetSelectedthis.setSelected$scope.setSelectednavtree-nodeitemSelectitemSelectnavtree-nodesetSelected

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

评论

1赞 user2165994 3/14/2013
谢谢rajkamal....我让它工作。我仍然对ngController从何而来感到困惑?require: "^ngController"
3赞 Rajkamal Subramanian 3/14/2013
一般而言,该语句查找定义了控制器的父元素。由于我们已经在上面定义了 控制器,因此它被作为第四个参数传递给 nav-node 指令的链接函数。您可以在 docs.angularjs.org/guide/directive 的“指令定义对象”标题下阅读有关此内容的信息。在这里,他们定义了选项。require: "^ngController"MyCtrlnavtree
3赞 Mark Rajcok 3/14/2013
+1.好。我还没有看到其他人在 SO 上举例说明使用.我看到的所有其他示例都需要 ngModel 或其他指令的控制器。require: '^ngController'
0赞 Julia Anne Jacobs 9/4/2013
+1 我希望我早点找到这个。能够使用“^ngController”将指令绑定到控制器真是太棒了。非常感谢@rajkamal!
3赞 George Stocker 2/19/2014
@rajkamal 请将您的答案发布到您的答案中,而不仅仅是通过 JSFiddle。如果 JSFiddle 出现故障,您的答案应保持其相关性。