如何在AngularJS中使用具有自己的作用域的自定义指令*来访问父范围?
我正在寻找任何方式访问指令中的“父”范围。 任何范围的组合,transclude,要求,从上面传递variables(或范围本身)等。我完全愿意向后弯曲,但我想避免一些完全hacky或不可维护的东西。 例如,我知道我现在可以通过从preLink参数中获取$scope
并遍历它的$sibling
作用域来find概念“parent”。
我真正想要的是能够在父范围中$watch
expression式。 如果我可以做到这一点,那么我可以在这里完成我想要做的: AngularJS – 如何渲染一个variables的部分?
一个重要的注意事项是指令必须在同一个父范围内可重用。 因此,默认行为(范围:false)不适用于我。 我需要一个单独的范围每个指令的实例,然后我需要$watch
一个variables,生活在父范围内。
代码示例值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? } } } }; });
请参阅AngularJS中范围原型/原型inheritance的细微差别?
总而言之,指令访问其父( $parent
)范围的方式取决于指令创build的范围的types:
-
默认(
scope: false
) – 指令不会创build新的范围,所以在这里没有inheritance。 指令的作用域与父/容器的作用域相同。 在链接函数中,使用第一个参数(通常是scope
)。 -
scope: true
– 该指令创build一个新的子范围,它从父范围原型inheritance。 在父范围上定义的属性可用于指令scope
(由于原型inheritance)。 只要小心写入一个原始的范围属性 – 这将在指令范围(隐藏/阴影同名的父范围属性)上创build一个新的属性。 -
scope: { ... }
– 该指令创build一个新的隔离/隔离范围。 它不会原型inheritance父范围。 您仍然可以使用$parent
来访问父范围,但通常不build议这样做。 相反,您应该使用=
,@
和&
表示法,通过指令所在的相同元素上的附加属性指定指令需要哪个父级范围属性(和/或函数)。 -
transclude: true
– 指令创build一个新的“transcluded”子范围,它从父范围原型inheritance。 如果指令还创build隔离范围,则隔行范围和隔离范围是兄弟。 每个作用域的$parent
属性引用相同的父作用域。
Angular v1.3更新 :如果指令也创build一个隔离范围,则被隔行范围现在是隔离范围的子级。 跨越和隔离范围不再是兄弟姐妹。 transcluded范围的$parent
属性现在引用隔离范围。
上面的链接有所有4种types的例子和图片。
你不能在指令的编译函数中访问范围(如下所述: https : //github.com/angular/angular.js/wiki/Understanding-Directives )。 你可以在链接函数中访问指令的作用域。
观看:
对于上面的1.和2.通常你通过一个属性来指定指令需要哪个父属性,然后$监视它:
<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(isolate scope),请使用@
或=
表示法来指定您指定的指令的名称:
<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() { ... });
访问控制器方法意味着从指令控制器/链接/范围访问父范围的方法。
如果指令是共享/inheritance父范围,那么直接调用一个父范围方法是非常简单的。
当您要从独立指令作用域访问父级作用域方法时,需要做更多的工作。
有几个选项(可能比下面列出的更多)从独立的指令作用域调用父作用域方法,或者监视父作用域variables(特别是选项#6 )。
请注意 ,我在这些例子中使用了link function
,但是您也可以根据需要使用directive controller
。
选项1。 通过Object literal和from指令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和双向绑定,可以更新父范围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
选项#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文档以获取关于指令的详细解释。
scope: false transclude: false
你将有相同的范围(与父元素)
$scope.$watch(...
如何根据这两个选项范围和传输来访问父范围有很多方法。
这里有一个我曾经使用的技巧:创build一个“虚拟”指令来保存父范围,并将其放置在所需指令之外的某个位置。 就像是:
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>
也许不是最优雅的解决scheme,但它完成了工作。
如果您使用ES6 Classes和ControllerAs
语法 ,则需要做一些稍微不同的事情。
请参阅下面的代码片段,并注意, vm
是在父HTML中使用的父Controller的ControllerAs
值
myApp.directive('name', function() { return { // no scope definition link : function(scope, element, attrs, ngModel) { scope.vm.func(...)
经过一切尝试,我终于想出了一个解决scheme。
只需将以下内容放在您的模板中:
{{currentDirective.attr = parentDirective.attr; ''}}
它只是写你想访问当前范围的父范围属性/variables。
还注意到; ''
; ''
在声明的最后,就是确保模板中没有输出。 (Angular评估每个语句,但只输出最后一个语句)。
这有点冒险,但是经过几个小时的反复试验,就完成了这项工作。