如何从AngularJS中具有自己的范围*的自定义指令*中访问父作用域?

How to access parent scope from within a custom directive *with own scope* in AngularJS?

我正在寻找在指令中访问"父"范围的任何方式。任何范围、超越、要求、从上面传入变量(或范围本身)的组合,等等。我完全愿意向后弯曲,但我想避免完全黑客或不可维护的东西。例如,我知道我现在可以通过从预链接参数中获取$scope并迭代它的$sibling范围来找到概念上的"父级"。

我真正想要的是能够在父范围中使用$watch表达式。如果我能做到这一点,那么我就可以完成我在这里要做的事情:AngularJS-如何用变量呈现分部?

一个重要的注意事项是,该指令必须在同一父范围内可重用。因此,默认行为(scope:false)对我不起作用。每个指令实例需要一个单独的作用域,然后需要$watch一个位于父作用域中的变量。

代码示例值1000个字,因此:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
app.directive('watchingMyParentScope', function() {
    return {
        require: /* ? */,
        scope: /* ? */,
        transclude: /* ? */,
        controller: /* ? */,
        compile: function(el,attr,trans) {
            // Can I get the $parent from the transclusion function somehow?
            return {
                pre: function($s, $e, $a, parentControl) {
                    // Can I get the $parent from the parent controller?
                    // By setting this.$scope = $scope from within that controller?

                    // Can I get the $parent from the current $scope?

                    // Can I pass the $parent scope in as an attribute and define
                    // it as part of this directive's scope definition?

                    // What don't I understand about how directives work and
                    // how their scope is related to their parent?
                },
                post: function($s, $e, $a, parentControl) {
                    // Has my situation improved by the time the postLink is called?
                }
            }
        }
    };
});

看看AngularJS中作用域原型/原型继承的细微差别是什么?

总结:指令访问其父($parent作用域的方式取决于指令创建的作用域类型:

  • 默认(scope: false)—该指令不创建新的作用域,因此这里没有继承。指令的作用域与父/容器的作用域相同。在link函数中,使用第一个参数(通常是scope)。

  • scope: true—该指令创建了一个新的子作用域,原型继承自父作用域。在父作用域上定义的属性可用于指令scope(因为原型继承)。只需注意写入基元作用域属性——这将在指令作用域上创建一个新属性(隐藏/隐藏同名的父作用域属性)。

  • scope: { ... }—该指令创建了一个新的隔离/隔离范围。它不典型地继承父范围。您仍然可以使用$parent访问父作用域,但通常不建议这样做。相反,您应该使用=@&表示法,通过使用指令的同一元素上的附加属性来指定指令需要哪些父作用域属性(和/或函数)。

  • transclude: true—该指令创建了一个新的"超越"子作用域,该子作用域的原型继承自父作用域。如果该指令还创建了一个隔离作用域,则被隔离的作用域和被隔离的作用域是同级的。每个作用域的$parent属性引用了相同的父作用域。angular v1.3更新:如果指令还创建了一个隔离作用域,则被隔离的作用域现在是该隔离作用域的子作用域。被屏蔽和隔离的范围不再是兄弟。被屏蔽作用域的$parent属性现在引用隔离作用域。

  • 以上链接包含所有4种类型的示例和图片。

    您不能访问指令编译函数中的作用域(如下所述:https://github.com/angular/angular.js/wiki/understanding directive s)。您可以在link函数中访问指令的作用域。

    看:

    1。2。上面:通常通过属性指定指令需要哪个父属性,然后$watch它:

    1
     
    1
    scope.$watch(attrs.attr1, function() { ... });

    如果您正在监视对象属性,则需要使用$parse:

    1
     
    1
    2
    var model = $parse(attrs.attr2);
    scope.$watch(model, function() { ... });

    3。在上面(隔离范围),注意使用@=符号给出的指令属性名称:

    1
     
    1
    2
    3
    4
    5
    6
    7
    scope: {
      localName3: '@attr3',
      attr4:      '='  // here, using the same name as the attribute
    },
    link: function(scope, element, attrs) {
       scope.$watch('localName3', function() { ... });
       scope.$watch('attr4',      function() { ... });


    访问控制器方法意味着从指令控制器/链接/作用域访问父作用域上的方法。

    如果该指令正在共享/继承父作用域,那么只调用父作用域方法是非常直接的。

    当您想从独立的指令作用域访问父作用域方法时,只需要做很少的工作。

    从独立指令作用域调用父作用域方法或监视父作用域变量(特别是选项6)的选项很少(可能比下面列出的多)。

    注意,我在这些示例中使用了link function,但您也可以根据需要使用directive controller

    选项1。通过对象文本和从指令HTML模板

    index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <!DOCTYPE html>
    <html ng-app="plunker">

      <head>
        <meta charset="utf-8" />
        AngularJS Plunker
        document.write('<base href="' + document.location + '" />');
        <link rel="stylesheet" href="style.css" />
        <script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9">
        <script src="app.js">
      </head>

      <body ng-controller="MainCtrl">
        <p>
    Hello {{name}}!
    </p>

        <p>
     Directive Content
    </p>
        <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged(selectedItems)" items="items"> </sd-items-filter>


        <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}}
    </p>

      </body>

    </html>

    itemfilterTemplate.html

    1
    2
    3
    <select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" ng-change="selectedItemsChanged({selectedItems:selectedItems})" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
      <option>--</option>
    </select>

    app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    var app = angular.module('plunker', []);

    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          items: '=',
          selectedItems: '=',
          selectedItemsChanged: '&'
        },
        templateUrl:"itemfilterTemplate.html"
      }
    })

    app.controller('MainCtrl', function($scope) {
      $scope.name = 'TARS';

      $scope.selectedItems = ["allItems"];

      $scope.selectedItemsChanged = function(selectedItems1) {
        $scope.selectedItemsReturnedFromDirective = selectedItems1;
      }

      $scope.items = [{
       "id":"allItems",
       "name":"All Items",
       "order": 0
      }, {
       "id":"CaseItem",
       "name":"Case Item",
       "model":"PredefinedModel"
      }, {
       "id":"Application",
       "name":"Application",
       "model":"Bank"
        }]

    });

    工作PLNKR:http://plnkr.co/edit/rgkusygdo9o3tewl6xgr?P=预览

    选项2。通过对象文本和从指令链接/作用域

    index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <!DOCTYPE html>
    <html ng-app="plunker">

      <head>
        <meta charset="utf-8" />
        AngularJS Plunker
        document.write('<base href="' + document.location + '" />');
        <link rel="stylesheet" href="style.css" />
        <script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9">
        <script src="app.js">
      </head>

      <body ng-controller="MainCtrl">
        <p>
    Hello {{name}}!
    </p>

        <p>
     Directive Content
    </p>
        <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged(selectedItems)" items="items"> </sd-items-filter>


        <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}}
    </p>

      </body>

    </html>

    itemfilterTemplate.html

    1
    2
    3
    4
    <select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;"
     ng-change="selectedItemsChangedDir()" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
      <option>--</option>
    </select>

    app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    var app = angular.module('plunker', []);

    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          items: '=',
          selectedItems: '=',
          selectedItemsChanged: '&'
        },
        templateUrl:"itemfilterTemplate.html",
        link: function (scope, element, attrs){
          scope.selectedItemsChangedDir = function(){
            scope.selectedItemsChanged({selectedItems:scope.selectedItems});  
          }
        }
      }
    })

    app.controller('MainCtrl', function($scope) {
      $scope.name = 'TARS';

      $scope.selectedItems = ["allItems"];

      $scope.selectedItemsChanged = function(selectedItems1) {
        $scope.selectedItemsReturnedFromDirective = selectedItems1;
      }

      $scope.items = [{
       "id":"allItems",
       "name":"All Items",
       "order": 0
      }, {
       "id":"CaseItem",
       "name":"Case Item",
       "model":"PredefinedModel"
      }, {
       "id":"Application",
       "name":"Application",
       "model":"Bank"
        }]
    });

    工作PLNKR:http://plnkr.co/edit/brvym2spbk9uxnicta?P=预览

    选项3。通过函数引用和从指令HTML模板

    index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <!DOCTYPE html>
    <html ng-app="plunker">

      <head>
        <meta charset="utf-8" />
        AngularJS Plunker
        document.write('<base href="' + document.location + '" />');
        <link rel="stylesheet" href="style.css" />
        <script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9">
        <script src="app.js">
      </head>

      <body ng-controller="MainCtrl">
        <p>
    Hello {{name}}!
    </p>

        <p>
     Directive Content
    </p>
        <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>


        <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnFromDirective}}
    </p>

      </body>

    </html>

    itemfilterTemplate.html

    1
    2
    3
    4
    <select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;"
     ng-change="selectedItemsChanged()(selectedItems)" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
      <option>--</option>
    </select>

    app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    var app = angular.module('plunker', []);

    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          items: '=',
          selectedItems:'=',
          selectedItemsChanged: '&'
        },
        templateUrl:"itemfilterTemplate.html"
      }
    })

    app.controller('MainCtrl', function($scope) {
      $scope.name = 'TARS';

      $scope.selectedItems = ["allItems"];

      $scope.selectedItemsChanged = function(selectedItems1) {
        $scope.selectedItemsReturnFromDirective = selectedItems1;
      }

      $scope.items = [{
       "id":"allItems",
       "name":"All Items",
       "order": 0
      }, {
       "id":"CaseItem",
       "name":"Case Item",
       "model":"PredefinedModel"
      }, {
       "id":"Application",
       "name":"Application",
       "model":"Bank"
        }]
    });

    工作PLNKR:http://plnkr.co/edit/jo6fcyvxcg3vh42biz?P=预览

    选项4。通过函数引用和从指令链接/作用域

    index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <!DOCTYPE html>
    <html ng-app="plunker">

      <head>
        <meta charset="utf-8" />
        AngularJS Plunker
        document.write('<base href="' + document.location + '" />');
        <link rel="stylesheet" href="style.css" />
        <script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9">
        <script src="app.js">
      </head>

      <body ng-controller="MainCtrl">
        <p>
    Hello {{name}}!
    </p>

        <p>
     Directive Content
    </p>
        <sd-items-filter selected-items="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>


        <P style="color:red">Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}}
    </p>

      </body>

    </html>

    itemfilterTemplate.html

    1
    2
    3
    <select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;" ng-change="selectedItemsChangedDir()" ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
      <option>--</option>
    </select>

    app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    var app = angular.module('plunker', []);

    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          items: '=',
          selectedItems: '=',
          selectedItemsChanged: '&'
        },
        templateUrl:"itemfilterTemplate.html",
        link: function (scope, element, attrs){
          scope.selectedItemsChangedDir = function(){
            scope.selectedItemsChanged()(scope.selectedItems);  
          }
        }
      }
    })

    app.controller('MainCtrl', function($scope) {
      $scope.name = 'TARS';

      $scope.selectedItems = ["allItems"];

      $scope.selectedItemsChanged = function(selectedItems1) {
        $scope.selectedItemsReturnedFromDirective = selectedItems1;
      }

      $scope.items = [{
       "id":"allItems",
       "name":"All Items",
       "order": 0
      }, {
       "id":"CaseItem",
       "name":"Case Item",
       "model":"PredefinedModel"
      }, {
       "id":"Application",
       "name":"Application",
       "model":"Bank"
        }]

    });

    工作PLNKR:http://plnkr.co/edit/bsqx2j1ycy86ijwanqf1?P=预览

    选项5:通过ng模型和双向绑定,可以更新父范围变量。因此,在某些情况下可能不需要调用父范围函数。

    index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <!DOCTYPE html>
    <html ng-app="plunker">

      <head>
        <meta charset="utf-8" />
        AngularJS Plunker
        document.write('<base href="' + document.location + '" />');
        <link rel="stylesheet" href="style.css" />
        <script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9">
        <script src="app.js">
      </head>

      <body ng-controller="MainCtrl">
        <p>
    Hello {{name}}!
    </p>

        <p>
     Directive Content
    </p>
        <sd-items-filter ng-model="selectedItems" selected-items-changed="selectedItemsChanged" items="items"> </sd-items-filter>


        <P style="color:red">Selected Items (in parent controller) set to: {{selectedItems}}
    </p>

      </body>

    </html>

    itemfilterTemplate.html

    1
    2
    3
    4
    <select ng-model="selectedItems" multiple="multiple" style="height: 200px; width: 250px;"
     ng-options="item.id as item.name group by item.model for item in items | orderBy:'name'">
      <option>--</option>
    </select>

    app.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    var app = angular.module('plunker', []);

    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          items: '=',
          selectedItems: '=ngModel'
        },
        templateUrl:"itemfilterTemplate.html"
      }
    })

    app.controller('MainCtrl', function($scope) {
      $scope.name = 'TARS';

      $scope.selectedItems = ["allItems"];

      $scope.items = [{
       "id":"allItems",
       "name":"All Items",
       "order": 0
      }, {
       "id":"CaseItem",
       "name":"Case Item",
       "model":"PredefinedModel"
      }, {
       "id":"Application",
       "name":"Application",
       "model":"Bank"
        }]
    });

    工作PLNKR:http://plnkr.co/edit/hnui3xgzdtnfcdzljihy?P=预览

    方案6:通过$watch$watchCollection。这是对items的双向绑定,在所有上述示例中,如果在父范围中修改了项,那么指令中的项也会反映出这些更改。

    如果您想从父作用域中观察其他属性或对象,可以使用下面给出的$watch$watchCollection来实现。

    HTML

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    <!DOCTYPE html>
    <html ng-app="plunker">

    <head>
      <meta charset="utf-8" />
      AngularJS Plunker
     
        document.write('<base href="' + document.location + '" />');
     
      <link rel="stylesheet" href="style.css" />
      <script data-require="[email protected]" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9">
      <script src="app.js">
    </head>

    <body ng-controller="MainCtrl">
      <p>
    Hello {{user}}!
    </p>
      <p>
    directive is watching name and current item
    </p>
      <table>
        <tr>
          <td>Id:</td>
          <td>
            <input type="text" ng-model="id" />
          </td>
        </tr>
        <tr>
          <td>Name:</td>
          <td>
            <input type="text" ng-model="name" />
          </td>
        </tr>
        <tr>
          <td>Model:</td>
          <td>
            <input type="text" ng-model="model" />
          </td>
        </tr>
      </table>

      <button style="margin-left:50px" type="buttun" ng-click="addItem()">Add Item</button>

      <p>
    Directive Contents
    </p>
      <sd-items-filter ng-model="selectedItems" current-item="currentItem" name="{{name}}" selected-items-changed="selectedItemsChanged" items="items"></sd-items-filter>

      <P style="color:red">Selected Items (in parent controller) set to: {{selectedItems}}
    </p>
    </body>

    </html>

    脚本App.js

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    app.directive('sdItemsFilter', function() {
      return {
        restrict: 'E',
        scope: {
          name: '@',
          currentItem: '=',
          items: '=',
          selectedItems: '=ngModel'
        },
        template: '<select ng-model="selectedItems" multiple="multiple" style="height: 140px; width: 250px;"' +
          'ng-options="item.id as item.name group by item.model for item in items | orderBy:\'name\'">' +
          '<option>--</option> </select>',
        link: function(scope, element, attrs) {
          scope.$watchCollection('currentItem', function() {
            console.log(JSON.stringify(scope.currentItem));
          });
          scope.$watch('name', function() {
            console.log(JSON.stringify(scope.name));
          });
        }
      }
    })

     app.controller('MainCtrl', function($scope) {
      $scope.user = 'World';

      $scope.addItem = function() {
        $scope.items.push({
          id: $scope.id,
          name: $scope.name,
          model: $scope.model
        });
        $scope.currentItem = {};
        $scope.currentItem.id = $scope.id;
        $scope.currentItem.name = $scope.name;
        $scope.currentItem.model = $scope.model;
      }

      $scope.selectedItems = ["allItems"];

      $scope.items = [{
       "id":"allItems",
       "name":"All Items",
       "order": 0
      }, {
       "id":"CaseItem",
       "name":"Case Item",
       "model":"PredefinedModel"
      }, {
       "id":"Application",
       "name":"Application",
       "model":"Bank"
      }]
    });

    关于指令的详细解释,您可以始终参考AngularJS文档。


    1
    2
     scope: false
     transclude: false

    您将拥有相同的作用域(带有父元素)

    1
    $scope.$watch(...

    根据这两个选项scope&exclude,有很多方法可以访问父作用域。


    这里有一个我曾经使用过的技巧:创建一个"伪"指令来保存父范围,并将其放在所需指令之外的某个地方。类似:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    module.directive('myDirectiveContainer', function () {
        return {
            controller: function ($scope) {
                this.scope = $scope;
            }
        };
    });

    module.directive('myDirective', function () {
        return {
            require: '^myDirectiveContainer',
            link: function (scope, element, attrs, containerController) {
                // use containerController.scope here...
            }
        };
    });

    然后

    1
     

    也许不是最优雅的解决方案,但它完成了任务。


    如果您使用ES6类和ControllerAs语法,则需要做一些稍微不同的事情。

    请参见下面的代码片段,注意vm是父HTML中使用的父控制器的ControllerAs值。

    1
    2
    3
    4
    5
    6
    myApp.directive('name', function() {
      return {
        // no scope definition
        link : function(scope, element, attrs, ngModel) {

            scope.vm.func(...)

    尝试了所有的方法之后,我终于想出了一个解决办法。

    只需在模板中放置以下内容:

    {{currentDirective.attr = parentDirective.attr; ''}}

    它只写入要访问当前作用域的父作用域属性/变量。

    还要注意,在语句末尾的; '',它是为了确保模板中没有输出。(Angular计算每个语句,但只输出最后一个语句)。

    这有点老土,但经过几个小时的反复试验,它确实做到了。