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

Posted

技术标签:

【中文标题】如何从 AngularJS 中的自定义指令 * 使用自己的范围 * 访问父范围?【英文标题】:How to access parent scope from within a custom directive *with own scope* in AngularJS? 【发布时间】:2013-07-27 20:03:53 【问题描述】:

我正在寻找在指令中访问“父”范围的任何方式。范围、嵌入、要求、从上面传入变量(或范围本身)等的任何组合。我完全愿意向后弯腰,但我想避免一些完全不合时宜或无法维护的事情。例如,我知道我现在可以通过从 preLink 参数中获取 $scope 并迭代它的 $sibling 范围来找到概念上的“父级”。

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

重要提示是指令必须在相同的父范围内可重用。因此,默认行为(范围: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?
                
            
        
    ;
);

【问题讨论】:

【参考方案1】:

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

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

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

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

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

    transclude: true - 该指令创建一个新的“transcluded”子作用域,其原型继承自父作用域。如果指令还创建了一个隔离范围,则转入和隔离范围是同级的。每个作用域的 $parent 属性引用相同的父作用域。Angular v1.3 更新:如果指令还创建了隔离作用域,则转入作用域现在是隔离作用域的子作用域.嵌入和隔离范围不再是兄弟。转入作用域的$parent 属性现在引用隔离作用域。

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

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

观看:

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

<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.(隔离范围),请注意使用 @= 表示法为指令属性指定的名称:

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

【讨论】:

谢谢你,马克。事实证明,我在How to render a partial with variables 上发布的解决方案确实非常有效。您真正需要将我链接到的内容是标题为“编写 html 的细微差别并认识到您的元素没有嵌套在您认为的 ng-controller 中”的内容。哇...菜鸟的错误。但这是对您解释范围的其他(更长的)答案的有用补充。 @collin,太好了,很高兴您解决了您的问题,因为我不太确定如何回复您的其他(现已删除)评论。 我可以/应该在scope.$watch('localName3', function() ...[?? WHAT TO DO HERE for example?] );中执行什么操作 @Andy,不,不要将$parse= 一起使用:fiddle。 $parse 仅用于非隔离作用域。 这是一个很好的答案,非常彻底。它也说明了为什么我只是讨厌使用 AngularJS。【参考方案2】:

尝试了一切,我终于想出了一个解决方案。

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

currentDirective.attr = parentDirective.attr; ''

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

还要注意语句末尾的; '',这是为了确保您的模板中没有输出。 (Angular 评估每条语句,但只输出最后一条)。

这有点老套,但经过几个小时的反复试验后,它就完成了。

【讨论】:

【参考方案3】:

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

查看下面的 sn-p 并注意 vm 是父 HTML 中使用的父 Controller 的 ControllerAs

myApp.directive('name', function() 
  return 
    // no scope definition
    link : function(scope, element, attrs, ngModel) 

        scope.vm.func(...)

【讨论】:

【参考方案4】:

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

如果指令共享/继承父作用域,那么直接调用父作用域方法就很简单了。

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

很少有选项(可能比下面列出的更多)可以从隔离指令范围调用父范围方法或监视父范围变量(特别是option#6)。

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

Option#1. 通过对象文字和来自指令 html 模板

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

选项#2。通过对象文字和来自指令链接/范围

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

选项#3。通过函数引用和来自指令 html 模板

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

选项#4。通过函数引用和指令链接/范围

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

选项#5:通过ng-model和双向绑定,可以更新父作用域变量。。因此,在某些情况下,您可能不需要调用父作用域函数。

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

选项#6:通过$watch$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>

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

【讨论】:

他为他的代表努力工作……为他的代表努力……他为他的代表努力工作,所以你最好给他投票。 downvoted - 答案中的任何有价值的信息由于其长度而无法访问 我用所有可用的替代方案回答了这个问题,并明确区分。在我看来,简短的回答并不总是有帮助,除非你有一个大局。 @YogeshManware:通过省略样式表等不相关的内容,不使用冗长的标记,简化示例以不使用“分组依据”等内容,可以大大缩短它。它也将是对每个示例进行某种解释非常有用。 这不是拒绝投票的理由。人们滥用此特权【参考方案5】:

这是我曾经使用过的一个技巧:创建一个“虚拟”指令来保存父作用域并将其放置在所需指令之外的某个位置。比如:

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>

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

【讨论】:

【参考方案6】:
 scope: false
 transclude: false

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

$scope.$watch(...

根据这两个选项范围和嵌入,有很多方法可以访问父范围。

【讨论】:

是的,简短而甜蜜,而且正确。它们似乎与父元素共享完全相同的范围......这使得它们无法在同一范围内重复使用。 jsfiddle.net/collindo/xqytH 很多时候我们在写可重用组件的时候需要隔离作用域,所以解决方法没那么简单

以上是关于如何从 AngularJS 中的自定义指令 * 使用自己的范围 * 访问父范围?的主要内容,如果未能解决你的问题,请参考以下文章

在 AngularJS 应用程序中拖放的自定义指令

带有错误消息的自定义验证器的 angularjs 指令

AngularJS中的指令

如何从 Vue.js 2 中的自定义指令中获取文字表达式?

AngularJS:$scope.$watch 没有更新从自定义指令上的 $resource 获取的值

将 keydown 事件定位到 angularJS 自定义指令