首页 文章

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

提问于
浏览
312

我完全愿意向后弯腰,但我想避免一些完全黑客或不可维护的东西 . 例如,我知道我现在可以通过从preLink参数获取 $scope 并迭代它的 $sibling 范围来找到概念"parent" .

我真正想要的是能够在父作用域中使用 $watch 表达式 . 如果我能做到这一点,那么我可以完成我在这里要做的事情:AngularJS - How to render a partial with variables?

An important note 是指令必须在同一父作用域内可重用 . 因此,默认行为(范围:false)对我不起作用 . 我需要每个指令实例的单独范围,然后我需要 $watch 一个存在于父范围内的变量 .

代码示例值1000个单词,因此:

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

6 回答

  • 620

    What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

    总结一下:指令访问其父( $parent )范围的方式取决于指令创建的范围类型:

    • default( scope: false ) - 该指令不创建新范围,因此这里没有继承 . 该指令的作用域与父/容器的作用域相同 . 在链接函数中,使用第一个参数(通常为 scope ) .

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

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

    • transclude: true - 该指令创建一个新的"transcluded"子作用域,它原型继承自父作用域 . 如果该指令还创建了隔离范围,则transcluded和隔离范围是兄弟 . 每个范围的 $parent 属性引用相同的父范围 .
      Angular v1.3 update :如果指令还创建了隔离范围,则transcluded范围现在是隔离范围的子代 . 被抄袭和隔离的范围不再是兄弟姐妹 . 现在,transcluded范围的 $parent 属性引用了隔离范围 .

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

    您无法访问指令的编译函数中的作用域(如此处所述:https://github.com/angular/angular.js/wiki/Understanding-Directives) . 您可以在链接功能中访问指令的范围 .

    Watching:

    对于1.和2.上面:通常你通过属性指定指令需要哪个父属性,然后$ watch it:

    <div my-dir attr1="prop1"></div>
    
    scope.$watch(attrs.attr1, function() { ... });
    

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

    <div my-dir attr2="obj.prop2"></div>
    
    var model = $parse(attrs.attr2);
    scope.$watch(model, function() { ... });
    

    对于3. above(隔离范围),使用 @= 表示法观察您给出指令属性的名称:

    <div my-dir attr3="{{prop3}}" attr4="obj.prop4"></div>
    
    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() { ... });
    
  • 0

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

    如果指令是共享/继承父作用域,则只需调用父作用域方法就可以了 .

    如果要从Isolated指令范围访问父作用域方法,则需要做更多的工作 .

    从隔离的指令范围调用父作用域方法或监视父作用域变量(特别是 option#6 ),几乎没有选项(可能比下面列出的更多) .

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

    Option#1. Through Object literal and from directive html template

    index.html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
      <head>
        <meta charset="utf-8" />
        <title>AngularJS Plunker</title>
        <script>document.write('<base href="' + document.location + '" />');</script>
        <link rel="stylesheet" href="style.css" />
        <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
        <script src="app.js"></script>
      </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

    <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

    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=preview

    Option#2. Through Object literal and from directive link/scope

    index.html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
      <head>
        <meta charset="utf-8" />
        <title>AngularJS Plunker</title>
        <script>document.write('<base href="' + document.location + '" />');</script>
        <link rel="stylesheet" href="style.css" />
        <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
        <script src="app.js"></script>
      </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

    <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

    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/BRvYm2SpSpBK9uxNIcTa?p=preview

    Option#3. Through Function reference and from directive html template

    index.html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
      <head>
        <meta charset="utf-8" />
        <title>AngularJS Plunker</title>
        <script>document.write('<base href="' + document.location + '" />');</script>
        <link rel="stylesheet" href="style.css" />
        <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
        <script src="app.js"></script>
      </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

    <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

    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/Jo6FcYfVXCCg3vH42BIz?p=preview

    Option#4. Through Function reference and from directive link/scope

    index.html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
      <head>
        <meta charset="utf-8" />
        <title>AngularJS Plunker</title>
        <script>document.write('<base href="' + document.location + '" />');</script>
        <link rel="stylesheet" href="style.css" />
        <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
        <script src="app.js"></script>
      </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

    <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

    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=preview

    Option#5: Through ng-model and two way binding, you can update parent scope variables. . 因此,在某些情况下,您可能不需要调用父作用域函数 .

    index.html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
      <head>
        <meta charset="utf-8" />
        <title>AngularJS Plunker</title>
        <script>document.write('<base href="' + document.location + '" />');</script>
        <link rel="stylesheet" href="style.css" />
        <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
        <script src="app.js"></script>
      </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

    <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

    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=preview

    Option#6: Through $watch and $watchCollection 在上述所有示例中, items 都是双向绑定,如果在父作用域中修改了项目,则指令中的项目也会反映更改 .

    如果要从父作用域中观察其他属性或对象,可以使用 $watch$watchCollection 执行此操作,如下所示

    html

    <!DOCTYPE html>
    <html ng-app="plunker">
    
    <head>
      <meta charset="utf-8" />
      <title>AngularJS Plunker</title>
      <script>
        document.write('<base href="' + document.location + '" />');
      </script>
      <link rel="stylesheet" href="style.css" />
      <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.9/angular.js" data-semver="1.3.9"></script>
      <script src="app.js"></script>
    </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>
    

    script app.js

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

    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文档以获取有关的详细说明指令 .

  • 4
    scope: false
     transclude: false
    

    你将拥有相同的范围(与父元素)

    $scope.$watch(...
    

    根据这两个选项范围和转换,有很多方法可以访问父作用域 .

  • 12

    这是我曾经使用过的一个技巧:创建一个“虚拟”指令来保存父范围并将其放在所需指令之外的某个位置 . 就像是:

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

    然后

    <div my-directive-container="">
        <div my-directive="">
        </div>
    </div>
    

    也许不是最优雅的解决方案,但它完成了工作 .

  • 48

    If you are using ES6 Classes and ControllerAs syntax ,你需要做一些稍微不同的事情 .

    请参阅下面的代码段并注意 vm 是父HTML中使用的父控制器的 ControllerAs

    myApp.directive('name', function() {
      return {
        // no scope definition
        link : function(scope, element, attrs, ngModel) {
    
            scope.vm.func(...)
    
  • 7

    尝试了一切后,我终于找到了解决方案 .

    只需将以下内容放入模板中:

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

    它只是写入您要访问当前范围的父范围属性/变量 .

    另请注意语句末尾的 ; '' ,它在模板中没有输出's to make sure there' . (Angular评估每个语句,但只输出最后一个语句) .

    这有点hacky,但经过几个小时的反复试验,它完成了这项工作 .

相关问题