Как получить доступ к родительской области из пользовательской директивы * с собственной областью * в AngularJS?
Я ищу любой способ доступа к "родительской" области видимости в директиве. Любая комбинация области видимости, transclude, require, передачи переменных (или самой области видимости) сверху и т. Д. Я полностью готов отклониться назад, но я хочу избежать чего-то совершенно хакерского или не поддерживаемого. Например, я знаю, что могу сделать это прямо сейчас, взяв $scope
из параметров preLink и итерации по нему $sibling
Области, чтобы найти концептуального "родителя".
Что я действительно хочу, так это уметь $watch
выражение в родительской области видимости. Если я могу сделать это, то я могу выполнить то, что я пытаюсь сделать здесь: AngularJS - Как сделать партиал с переменными?
Важным примечанием является то, что директива должна использоваться повторно в той же родительской области. Поэтому поведение по умолчанию (scope: 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 ответов
См. Каковы нюансы области действия прототипного / прототипического наследования в AngularJS?
Подводя итог: способ, которым директива обращается к своему родителю ($parent
) область действия зависит от типа области действия, создаваемой директивой:
дефолт (
scope: false
) - директива не создает новую область видимости, поэтому здесь нет наследования. Область действия директивы такая же, как у родительского элемента / контейнера. В функции ссылки используйте первый параметр (обычноscope
).scope: true
- директива создает новую дочернюю область, которая прототипно наследуется от родительской области. Свойства, определенные в родительской области, доступны для директивыscope
(из-за наследования прототипа). Остерегайтесь записи в примитивное свойство области действия - это создаст новое свойство в области действия директивы (которая скрывает / скрывает родительское свойство области с тем же именем).scope: { ... }
- директива создает новую изолированную / изолированную область. Он не наследует прототип родительской области видимости. Вы все еще можете получить доступ к родительской области, используя$parent
, но это обычно не рекомендуется. Вместо этого вы должны указать, какие родительские свойства области (и / или функцию) нужны директиве, через дополнительные атрибуты того же элемента, где используется директива, используя=
,@
, а также&
нотации.transclude: true
- директива создает новую "включенную" дочернюю область, которая прототипически наследуется от родительской области. Если директива также создает изолированную область видимости, то включенные и изолированные области являются братьями и сестрами.$parent
свойство каждой области ссылается на одну и ту же родительскую область.
Обновление Angular v1.3: если директива также создает изолированную область, то включенная область теперь является дочерней для изолированной области. Трансклюзивные и изолирующие области больше не являются родными братьями.$parent
свойство включенной области теперь ссылается на область изоляции.
Приведенная выше ссылка содержит примеры и изображения всех 4 типов.
Вы не можете получить доступ к области действия в функции компиляции директивы (как упомянуто здесь: https://github.com/angular/angular.js/wiki/Understanding-Directives). Вы можете получить доступ к области действия директивы в функции ссылки.
Наблюдение:
Для 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. выше (изолировать область видимости) следите за именем, которое вы даете свойству директивы, используя @
или же =
обозначения:
<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() { ... });
Доступ к методу контроллера означает доступ к методу в родительской области действия из директивы controller / link / scope.
Если директива разделяет / наследует родительскую область видимости, тогда достаточно просто вызвать метод родительской области видимости.
Немного больше работы требуется, если вы хотите получить доступ к методу родительской области действия из области действия изолированной директивы.
Существует несколько опций (может быть больше перечисленных ниже) для вызова метода родительской области из изолированной области действия директив или просмотра переменных родительской области (специально для опции № 6).
Обратите внимание, что я использовал link function
в этих примерах, но вы можете использовать directive controller
а также на основе требований.
Опция 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. Через литерал объекта и из директивы 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
Вариант № 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. Через ссылку на функцию и из директивы 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
Вариант № 5: с помощью ng-модели и двухсторонней привязки вы можете обновить родительские переменные области видимости., Поэтому в некоторых случаях вам может не потребоваться вызывать родительские функции области видимости.
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(...
Есть много способов получить доступ к родительской области видимости, в зависимости от этих двух параметров: видимость и включение.
Вот трюк, который я использовал один раз: создайте директиву "dummy" для хранения родительской области и поместите ее где-нибудь за пределами желаемой директивы. Что-то вроде:
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>
Возможно, не самое изящное решение, но оно сделало свою работу.
Если вы используете классы ES6 и ControllerAs
синтаксис, вам нужно сделать что-то немного другое.
Смотрите фрагмент ниже и обратите внимание, что vm
это ControllerAs
значение родительского контроллера, как используется в родительском HTML
myApp.directive('name', function() {
return {
// no scope definition
link : function(scope, element, attrs, ngModel) {
scope.vm.func(...)
Перепробовав все, я наконец-то нашел решение.
Просто поместите следующее в ваш шаблон:
{{currentDirective.attr = parentDirective.attr; ''}}
Он просто записывает родительский атрибут / переменную области, к которой вы хотите получить доступ к текущей области.
Также обратите внимание на ; ''
в конце инструкции нужно убедиться, что в вашем шаблоне нет вывода. (Angular оценивает каждое утверждение, но выводит только последнее).
Это немного глупо, но после нескольких часов проб и ошибок, оно делает свою работу.