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

时间:2022-06-18 13:33:34

I'm trying to generate an n-level hierarchical unordered list with anugularJS, and have been able to successfully do so. But now, I'm having scope issues between the directive and controller. I need to change a scope property of the parent from within a function called via ng-click in the directive template.

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

See http://jsfiddle.net/ahonaker/ADukg/2046/ - here's the JS

参见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);
            }
        }
    };
});

and here's the HTML

这是HTML

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

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

Note the list is generated from the model. And ng-click calls the function to set the parent scope property (itemselected), but the change only occurs locally. Expected behavior, when I click on an item, is that "Selected: None" should change to "Selected: xxx" where xxx is the item that was clicked.

注意,列表是由模型生成的。ng-click调用函数来设置父范围属性(itemselected),但是更改只在本地发生。当我单击一个项目时,期望的行为是“选择:None”应该更改为“选择:xxx”,其中xxx是被单击的项目。

Am I not binding the property between the parent scope and the directive appropriately? How do I pass the property change to the parent scope?

我是否在父作用域和指令之间绑定了属性?如何将属性更改传递给父范围?

Hope this is clear.

希望这是清楚的。

Thanks in advance for any help.

谢谢你的帮助。

3 个解决方案

#1


18  

Please have a look at this working fiddle, http://jsfiddle.net/eeuSv/

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

What i did was to require the parent controller inside the navtree-node directive, and call a member function defined in that controller. The member function is setSelected. Please note that its this.setSelected and not $scope.setSelected. Then define a navtree-node scope method itemSelect. While you click on the anchor tags, it will call the itemSelect method on the navtree-node scope. This inturn will call the controllers member method setSelected passing the selected id.

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

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

范围。项选择= function(id){myGreatParentControler.setSelected(id)}

#2


11  

Maxdec is right, this has to do with scoping. Unfortunately, this is a case that's complicated enough that the AngularJS docs can be mis-leading for a beginner (like myself).

Maxdec是对的,这与范围有关。不幸的是,这是一个非常复杂的情况,AngularJS文档可能会误导初学者(像我一样)。

Warning: brace yourself for me being a little long-winded as I attempt to explain this. If you just want to see the code, go to this JSFiddle. I've also found the egghead.io videos invaluable in learning about AngularJS.

警告:在我试图解释这一点的时候,你要做好准备,让我长篇大论。如果您只想看到代码,请转到JSFiddle。我也找到了egghead。io视频在学习AngularJS中是无价的。

Here's my understanding of the problem: you have a hierarchy of directives (navtree, navitem) and you want to pass information from the navitem "up the tree" to the root controller. AngularJS, like well-written Javascript in general, is set up to be strict about the scope of your variables, so that you don't accidentally mess up other scripts also running on the page.

这里是我对这个问题的理解:您有一个层次的指令(navtree, navitem),并且您希望将信息从navitem“上树”传递到根控制器。像一般编写好的Javascript一样,AngularJS被设置为对变量的范围严格,这样您就不会不小心把其他脚本弄乱了。

There's a special syntax (&) in Angular that lets you both create an isolate scope and call a function on the parent scope:

有一个特殊的语法(&)的角度,让您创建一个隔离范围和调用一个函数在父范围:

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

So far so good. Things get tricky when you have multiple levels of directives, because you essentially want to do the following:

目前为止一切都很顺利。当你有多个层次的指令时,事情会变得很棘手,因为你基本上想做以下事情:

  1. Have a function in the root controller that accepts a variable and update the model
  2. 在根控制器中有一个接受变量并更新模型的函数吗
  3. A mid-level directive
  4. 中层的指令
  5. A child-level directive that can communicate with the root controller
  6. 可以与根控制器通信的子级指令

The problem is, the child-level directive cannot see the root controller. My understanding is that you have to set up a "chain" in your directive structure that acts as follows:

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

First: Have a function in your root controller that returns a function (which has reference to the root view controller's scope):

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

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

Second: Set up the mid-level directive to have it's own select function (which it will pass to the child) that returns something like the following. Notice how we have to save off the scope of the mid-level directive, because when this code is actually executed, it will be in the context of the child-level directive:

第二:设置中间层指令,使其具有自己的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);
    };
};

Third: In the child-level directive (navtreeNode) bind a function to ng-click that calls a local function, which will, in turn, "call up the chain" all the way to the root controller:

第三:在子级指令(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);
};

Here's the updated fork of your JSFiddle, which has comments in the code.

下面是更新的JSFiddle,它在代码中有注释。

#3


3  

It may be because each directive creates his own scope (actually you tell them to do so).
You can read more about directives here, especially the chapter "Writing directives (long version)".

这可能是因为每个指令都创建了他自己的范围(实际上您告诉他们这样做)。您可以在这里阅读更多关于指令的内容,特别是“编写指令(长版本)”一章。

scope - If set to:

范围-如果设置为:

true - then a new scope will be created for this directive. If multiple directives on the same element request a new scope, only one new scope is created. The new scope rule does not apply for the root of the template since the root of the template always gets a new scope.

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

{} (object hash) - then a new 'isolate' scope is created. The 'isolate' scope differs from normal scope in that it does not prototypically inherit from the parent scope. This is useful when creating reusable components, which should not accidentally read or modify data in the parent scope.

{}(对象散列)——然后创建一个新的“隔离”范围。“隔离”范围不同于正常范围,因为它没有从父范围继承原型。这在创建可重用组件时非常有用,因为这些组件不应该意外地在父范围内读取或修改数据。

So the changes you do are not reflected in the MyCtrl scope because each directive has his own 'isolated' scope.

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

That's why a click only changes the local $scope.itemselected variable and not 'all' of them.

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

#1


18  

Please have a look at this working fiddle, http://jsfiddle.net/eeuSv/

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

What i did was to require the parent controller inside the navtree-node directive, and call a member function defined in that controller. The member function is setSelected. Please note that its this.setSelected and not $scope.setSelected. Then define a navtree-node scope method itemSelect. While you click on the anchor tags, it will call the itemSelect method on the navtree-node scope. This inturn will call the controllers member method setSelected passing the selected id.

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

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

范围。项选择= function(id){myGreatParentControler.setSelected(id)}

#2


11  

Maxdec is right, this has to do with scoping. Unfortunately, this is a case that's complicated enough that the AngularJS docs can be mis-leading for a beginner (like myself).

Maxdec是对的,这与范围有关。不幸的是,这是一个非常复杂的情况,AngularJS文档可能会误导初学者(像我一样)。

Warning: brace yourself for me being a little long-winded as I attempt to explain this. If you just want to see the code, go to this JSFiddle. I've also found the egghead.io videos invaluable in learning about AngularJS.

警告:在我试图解释这一点的时候,你要做好准备,让我长篇大论。如果您只想看到代码,请转到JSFiddle。我也找到了egghead。io视频在学习AngularJS中是无价的。

Here's my understanding of the problem: you have a hierarchy of directives (navtree, navitem) and you want to pass information from the navitem "up the tree" to the root controller. AngularJS, like well-written Javascript in general, is set up to be strict about the scope of your variables, so that you don't accidentally mess up other scripts also running on the page.

这里是我对这个问题的理解:您有一个层次的指令(navtree, navitem),并且您希望将信息从navitem“上树”传递到根控制器。像一般编写好的Javascript一样,AngularJS被设置为对变量的范围严格,这样您就不会不小心把其他脚本弄乱了。

There's a special syntax (&) in Angular that lets you both create an isolate scope and call a function on the parent scope:

有一个特殊的语法(&)的角度,让您创建一个隔离范围和调用一个函数在父范围:

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

So far so good. Things get tricky when you have multiple levels of directives, because you essentially want to do the following:

目前为止一切都很顺利。当你有多个层次的指令时,事情会变得很棘手,因为你基本上想做以下事情:

  1. Have a function in the root controller that accepts a variable and update the model
  2. 在根控制器中有一个接受变量并更新模型的函数吗
  3. A mid-level directive
  4. 中层的指令
  5. A child-level directive that can communicate with the root controller
  6. 可以与根控制器通信的子级指令

The problem is, the child-level directive cannot see the root controller. My understanding is that you have to set up a "chain" in your directive structure that acts as follows:

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

First: Have a function in your root controller that returns a function (which has reference to the root view controller's scope):

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

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

Second: Set up the mid-level directive to have it's own select function (which it will pass to the child) that returns something like the following. Notice how we have to save off the scope of the mid-level directive, because when this code is actually executed, it will be in the context of the child-level directive:

第二:设置中间层指令,使其具有自己的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);
    };
};

Third: In the child-level directive (navtreeNode) bind a function to ng-click that calls a local function, which will, in turn, "call up the chain" all the way to the root controller:

第三:在子级指令(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);
};

Here's the updated fork of your JSFiddle, which has comments in the code.

下面是更新的JSFiddle,它在代码中有注释。

#3


3  

It may be because each directive creates his own scope (actually you tell them to do so).
You can read more about directives here, especially the chapter "Writing directives (long version)".

这可能是因为每个指令都创建了他自己的范围(实际上您告诉他们这样做)。您可以在这里阅读更多关于指令的内容,特别是“编写指令(长版本)”一章。

scope - If set to:

范围-如果设置为:

true - then a new scope will be created for this directive. If multiple directives on the same element request a new scope, only one new scope is created. The new scope rule does not apply for the root of the template since the root of the template always gets a new scope.

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

{} (object hash) - then a new 'isolate' scope is created. The 'isolate' scope differs from normal scope in that it does not prototypically inherit from the parent scope. This is useful when creating reusable components, which should not accidentally read or modify data in the parent scope.

{}(对象散列)——然后创建一个新的“隔离”范围。“隔离”范围不同于正常范围,因为它没有从父范围继承原型。这在创建可重用组件时非常有用,因为这些组件不应该意外地在父范围内读取或修改数据。

So the changes you do are not reflected in the MyCtrl scope because each directive has his own 'isolated' scope.

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

That's why a click only changes the local $scope.itemselected variable and not 'all' of them.

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