Wie kann ich in AngularJS den übergeordneten Bereich innerhalb einer benutzerdefinierten statement * mit eigenem Gültigkeitsbereich * aufrufen?

Ich suche nach einer beliebigen Art des Zugriffs auf den Bereich “Eltern” innerhalb einer Direktive. Jede Kombination von scope, Transclude, Requirieren, Eingeben von Variablen (oder dem Scope selbst) von oben, etc. Ich bin total bereit, mich rückwärts zu beugen, aber ich möchte etwas total Hackiges oder Unerwartetes vermeiden. Zum Beispiel weiß ich, dass ich es jetzt tun könnte, indem ich den $scope von den preLink-Parametern nehme und über seine $sibling Bereiche iteriere, um den konzeptionellen “Elternteil” zu finden.

Was ich wirklich möchte, ist in der Lage zu sein $watch einen Ausdruck im übergeordneten Bereich zu beobachten. Wenn ich das kann, dann kann ich erreichen, was ich hier versuche: AngularJS – Wie kann man Partial mit Variablen rendern?

Eine wichtige Anmerkung ist, dass die Direktive innerhalb des gleichen übergeordneten Bereichs wiederverwendbar sein muss. Daher funktioniert das Standardverhalten (scope: false) nicht für mich. Ich brauche einen individuellen scope pro Instanz der Direktive, und dann muss ich eine Variable beobachten, die im übergeordneten Bereich lebt.

Ein Codebeispiel ist 1000 Wörter wert, also:

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

   

Siehe Was sind die Nuancen des scopes prototypische / prototypische inheritance in AngularJS?

Zusammenfassend: Wie eine Direktive auf ihren übergeordneten ( $parent ) Bereich zugreift, hängt vom Typ des Bereichs ab, den die Direktive erstellt:

  1. Standardwert ( scope: false ) – Die Direktive erstellt keinen neuen Bereich, daher gibt es hier keine inheritance. Der Geltungsbereich der Richtlinie entspricht dem des übergeordneten / des Containers. Verwenden Sie in der Verknüpfungsfunktion den ersten Parameter (normalerweise scope ).

  2. scope: true – Die Direktive erstellt einen neuen untergeordneten Bereich, der prototypisch vom übergeordneten Bereich erbt. Eigenschaften, die im übergeordneten Bereich definiert sind, sind für den Bereich der Direktive verfügbar (wegen der prototypischen inheritance). Achten Sie jedoch darauf, in eine primitive Bereichseigenschaft zu schreiben – dies wird eine neue Eigenschaft im Direktivenbereich erstellen (die die Eigenschaft des übergeordneten Bereichs mit demselben Namen ver- / versteckt).

  3. scope: { ... } – Die statement erstellt einen neuen isolate / isolated-Bereich. Prototyp erbt den übergeordneten Bereich nicht prototypisch. Sie können weiterhin über $parent auf den übergeordneten Bereich zugreifen, was normalerweise nicht empfohlen wird. Stattdessen sollten Sie angeben, welche Eigenschaften des übergeordneten Bereichs (und / oder der function) die statement über zusätzliche Attribute für dasselbe Element benötigt, für das die statement verwendet wird, und zwar mit der Schreibweise = , @ und & .

  4. transclude: true – Die Direktive erstellt einen neuen “transcluded” Kindbereich, der prototypisch vom übergeordneten Bereich erbt. Wenn die Direktive auch einen Isolate-Bereich erstellt, sind die umschlossenen und die Isolat-Bereiche Geschwister. Die $parent Eigenschaft jedes Bereichs verweist auf denselben übergeordneten Bereich.
    Angular v1.3 update : Wenn die Direktive auch einen isolate-Bereich erstellt, ist der übermittelte Bereich jetzt ein untergeordneter Bereich des isolate-Bereichs. Die umschlossenen und isolierenden Bereiche sind nicht länger Geschwister. Die $parent Eigenschaft des übergangenen Bereichs verweist jetzt auf den isolate-Bereich.

Der obige Link enthält Beispiele und Bilder aller 4 Arten.

Sie können nicht auf den Bereich in der Kompilierungsfunktion der Richtlinie zugreifen (wie hier erwähnt: https://github.com/angular/angular.js/wiki/Understanding-Directives ). Sie können auf den Geltungsbereich der Richtlinie in der Verknüpfungsfunktion zugreifen.

Aufpassen:

Für 1. und 2. oben: Normalerweise geben Sie an, welche Elterneigenschaft die Direktive über ein Attribut benötigt, und dann beobachten Sie es:

 

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

Wenn Sie eine Objekteigenschaft beobachten, müssen Sie $ parse verwenden:

 

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

Für 3. oben (isolieren Sie den Gültigkeitsbereich) beobachten Sie den Namen, den Sie der directive -Eigenschaft geben, indem Sie die Notation @ oder = verwenden:

 

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

Der Zugriff auf die Controller-Methode bedeutet den Zugriff auf eine Methode im übergeordneten Bereich über den Direktiven-Controller / Link / Bereich.

Wenn die Direktive den Parent-Bereich teilt / erbt, ist es ziemlich einfach, einfach eine Parent-Scope-Methode aufzurufen.

Etwas mehr Arbeit ist erforderlich, wenn Sie über den Isolated-Richtlinienbereich auf die übergeordnete Bereichsmethode zugreifen möchten.

Es gibt wenige Optionen (möglicherweise mehr als die unten aufgeführten), um eine Parent-Scope-Methode aus dem Bereich der isolierten Direktiven oder der übergeordneten Scope-Variablen (insbesondere Option 6 ) aufzurufen.

Beachten Sie, dass ich in diesen Beispielen die link function verwendet habe, Sie können jedoch je nach Bedarf auch einen directive controller .

Option 1. Über das Objekt literal und über die Direktive html-Vorlage

index.html

 < !DOCTYPE html>    AngularJS Plunker       

Hello {{name}}!

Directive Content

Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}}

itemfilterTemplate.html

  

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" }] }); 

Arbeiten PLNKR: http://plnkr.co/edit/rgKUsYGDo9O3tewL6xgr?p=preview

Option 2. Über das Objekt literal und über die statement link / scope

index.html

 < !DOCTYPE html>    AngularJS Plunker       

Hello {{name}}!

Directive Content

Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}}

itemfilterTemplate.html

  

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" }] }); 

Arbeiten PLNKR: http://plnkr.co/edit/BRvYm2SpSpBK9uxNIcTa?p=preview

Option # 3. Durch functionsreferenz und aus Direktiven-HTML-Vorlage

index.html

 < !DOCTYPE html>    AngularJS Plunker       

Hello {{name}}!

Directive Content

Selected Items (in parent controller) set to: {{selectedItemsReturnFromDirective}}

itemfilterTemplate.html

  

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" }] }); 

arbeiten plnrr: http://plnkr.co/edit/Jo6FcYfVXCCg3vH42BIz?p=preview

Option # 4. Durch function reference und von directive link / scope

index.html

 < !DOCTYPE html>    AngularJS Plunker       

Hello {{name}}!

Directive Content

Selected Items (in parent controller) set to: {{selectedItemsReturnedFromDirective}}

itemfilterTemplate.html

  

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" }] }); 

Working PLNKR: http://pnnr.co/edit/BSqx2J1yCY86IJwAnQuF1?p=preview

Option # 5: Durch ng-model und bidirektionale Bindung können Sie die übergeordneten Bereichsvariablen aktualisieren. . Daher müssen Sie möglicherweise in einigen Fällen nicht die übergeordneten Bereichsfunktionen aufrufen.

index.html

 < !DOCTYPE html>    AngularJS Plunker       

Hello {{name}}!

Directive Content

Selected Items (in parent controller) set to: {{selectedItems}}

itemfilterTemplate.html

  

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" }] }); 

Working PLNKR: http://plnkr.co/edit/hNui3xgzdTnfcdzljihY?p=preview

Option # 6: Durch $watch und $watchCollection Es ist eine Zwei-Wege-Bindung für items in allen obigen Beispielen. Wenn Elemente im übergeordneten Bereich geändert werden, würden Elemente in der Direktive auch die Änderungen widerspiegeln.

Wenn Sie andere Attribute oder Objekte aus dem übergeordneten Bereich betrachten möchten, können Sie das mit $watch und $watchCollection tun, wie unten angegeben

html

 < !DOCTYPE html>    AngularJS Plunker       

Hello {{user}}!

directive is watching name and current item

Id:
Name:
Model:

Directive Contents

Selected Items (in parent controller) set to: {{selectedItems}}

Skript app.js

var app = angular.module (‘bottler’, []);

 app.directive('sdItemsFilter', function() { return { restrict: 'E', scope: { name: '@', currentItem: '=', items: '=', selectedItems: '=ngModel' }, template: '', 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" }] }); 

Für detaillierte Erläuterungen zu Direktiven können Sie immer auf die AngularJs-Dokumentation verweisen.

  scope: false transclude: false 

und Sie haben den gleichen scope (mit Elternelement)

 $scope.$watch(... 

Es gibt viele Möglichkeiten, wie Sie auf den übergeordneten Bereich zugreifen können, abhängig von den beiden Optionen scope & transclude.

Hier ist ein Trick, den ich einmal verwendet habe: Erstelle eine “Dummy” -Direktive, um den übergeordneten Bereich zu halten und ihn irgendwo außerhalb der gewünschten Direktive zu platzieren. Etwas wie:

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

und dann

 

Vielleicht nicht die eleganteste Lösung, aber es hat den Job erledigt.

Wenn Sie ES6-classn und ControllerAs Syntax verwenden , müssen Sie etwas anderes tun.

Sehen Sie sich das folgende Snippet an und beachten Sie, dass vm der ControllerAs Wert des übergeordneten Controllers ist, wie er im übergeordneten HTML verwendet wird

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

Nachdem ich alles versucht hatte, fand ich endlich eine Lösung.

Platzieren Sie einfach Folgendes in Ihre Vorlage:

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

Es schreibt nur das Attribut / die Variable des übergeordneten Bereichs, auf die Sie zugreifen möchten, auf den aktuellen Bereich.

Beachten Sie auch die ; '' Am Ende der statement wird sichergestellt, dass in Ihrer Vorlage keine Ausgabe erfolgt. (Angular wertet jede statement aus, gibt aber nur die letzte aus).

Es ist ein bisschen hacky, aber nach ein paar Stunden Versuch und Irrtum, macht es den Job.