wake-up-neo.com

Wie greife ich in AngularJS über eine benutzerdefinierte Direktive * mit eigenem Gültigkeitsbereich * auf den übergeordneten Gültigkeitsbereich zu?

Ich suche nach einer Möglichkeit, innerhalb einer Direktive auf den "übergeordneten" Bereich zuzugreifen. Beliebige Kombinationen von Gültigkeitsbereichen, Transkludieren, Erfordernissen, Übergeben von Variablen (oder des Gültigkeitsbereichs selbst) von oben usw. Ich bin absolut bereit, mich nach hinten zu beugen, aber ich möchte etwas vermeiden, das absolut hackig oder nicht zu pflegen ist. Ich weiß zum Beispiel, dass ich das jetzt tun kann, indem ich den $scope aus den preLink-Parametern nehme und über den Geltungsbereich von $sibling iteriere, um das konzeptionelle "übergeordnete Element" zu finden.

Was ich wirklich möchte, ist, in der Lage zu sein, einen Ausdruck im übergeordneten Bereich zu $watch. Wenn ich das kann, kann ich das erreichen, was ich hier versuche: AngularJS - Wie rendere ich einen Teil mit Variablen?

Ein wichtiger Hinweis ist, dass die Direktive im selben übergeordneten Bereich wiederverwendbar sein muss. Daher funktioniert das Standardverhalten (scope: false) bei mir nicht. Ich brauche einen individuellen Geltungsbereich pro Instanz der Direktive und dann muss ich eine Variable $watch, die im übergeordneten Geltungsbereich lebt.

Ein Codebeispiel hat einen Wert von 1000 Wörtern.

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

Siehe Was sind die Nuancen der prototypischen/prototypischen Vererbung des Gültigkeitsbereichs in AngularJS?

Zusammenfassend lässt sich sagen, dass die Art und Weise, in der eine Direktive auf den übergeordneten Bereich ($parent) zugreift, von der Art des Bereichs abhängt, den die Direktive erstellt:

  1. default (scope: false) - Die Direktive erstellt keinen neuen Bereich, daher gibt es hier keine Vererbung. Der Geltungsbereich der Direktive ist derselbe wie der des übergeordneten 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, stehen der Direktive scope zur Verfügung (aufgrund der prototypischen Vererbung). Achten Sie nur darauf, dass Sie nicht in eine primitive Bereichseigenschaft schreiben. Dadurch wird eine neue Eigenschaft für den Direktivenbereich erstellt (die die gleichnamige übergeordnete Bereichseigenschaft verbirgt/verdunkelt).

  3. scope: { ... } - Die Direktive erstellt einen neuen isolierten/isolierten Bereich. Der übergeordnete Bereich wird nicht prototypisch übernommen. Sie können weiterhin mit $parent auf den übergeordneten Bereich zugreifen, dies wird jedoch normalerweise nicht empfohlen. Stattdessen sollten Sie angeben, welche übergeordneten Bereichseigenschaften (und/oder Funktionen) die Direktive über zusätzliche Attribute für dasselbe Element benötigt, in dem die Direktive verwendet wird. Verwenden Sie hierzu =, @ und & Notation.

  4. transclude: true - Die Direktive erstellt einen neuen "übergeordneten" untergeordneten Bereich, der prototypisch vom übergeordneten Bereich erbt. Wenn die Direktive auch einen isolierten Bereich erstellt, sind der übertragene und der isolierte Bereich Geschwister. Die $parent -Eigenschaft jedes Bereichs verweist auf denselben übergeordneten Bereich.
    Angular v1.3-Update : Wenn die Direktive auch einen isolierten Bereich erstellt, ist der übermittelte Bereich nun ein untergeordnetes Element des isolierten Bereichs. Die abgelegenen und isolierten Bereiche sind keine Geschwister mehr. Die $parent -Eigenschaft des übermittelten Bereichs verweist jetzt auf den isolierten Bereich.

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

Sie können in der Kompilierungsfunktion der Direktive nicht auf den Gültigkeitsbereich zugreifen (wie hier erwähnt: https://github.com/angular/angular.js/wiki/Understanding-Directives ). Sie können über die Link-Funktion auf den Geltungsbereich der Direktive zugreifen.

Anschauen:

Für 1. und 2. oben: Normalerweise geben Sie über ein Attribut an, welche übergeordnete Eigenschaft die Direktive benötigt, und dann $ watch it:

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

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

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

Beobachten Sie für 3. oben (Bereich isolieren) den Namen, den Sie der Direktive-Eigenschaft geben, indem Sie die Notation @ oder = verwenden:

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

Zugriff auf Controller-Methode bedeutet Zugriff auf eine Methode im übergeordneten Bereich über die Direktive controller/link/scope.

Wenn die Direktive den übergeordneten Bereich freigibt/erbt, ist es ganz einfach, nur eine übergeordnete Bereichsmethode aufzurufen.

Wenig mehr Arbeit ist erforderlich, wenn Sie aus dem Geltungsbereich der isolierten Direktive auf die übergeordnete Geltungsbereichsmethode zugreifen möchten.

Es gibt nur wenige Optionen (möglicherweise mehr als die unten aufgeführten), um eine übergeordnete Gültigkeitsbereichsmethode aus dem isolierten Gültigkeitsbereich von Direktiven aufzurufen oder übergeordnete Gültigkeitsbereichsvariablen zu überwachen (, Option 6 ).

Beachten Sie , dass ich in diesen Beispielen _link function_ verwendet habe, Sie können jedoch je nach Anforderung auch einen _directive controller_ verwenden.

Option # 1. Durch Objektliteral und aus Direktiven-HTML-Vorlage

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="[email protected]" 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"
    }]

});
_

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

Option 2. Durch Objektliteral und aus Direktive 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="[email protected]" 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"
    }]
});
_

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

Option 3. Durch Funktionsreferenz und aus Direktiven-HTML-Vorlage

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="[email protected]" 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"
    }]
});
_

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

Option 4. Durch Funktionsreferenz und aus Direktivenlink/Geltungsbereich

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="[email protected]" 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"
    }]

});
_

working plnkr: http://plnkr.co/edit/BSqx2J1yCY86IJwAnQF1?p=preview

Option 5: Durch ng-model und bidirektionale Bindung können Sie übergeordnete Bereichsvariablen aktualisieren. . In einigen Fällen müssen Sie daher möglicherweise keine übergeordneten Bereichsfunktionen aufrufen.

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="[email protected]" 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"
    }]
});
_

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

Option # 6: Durch _$watch_ und _$watchCollection_ In allen obigen Beispielen ist dies eine bidirektionale Bindung für items. Wenn Elemente im übergeordneten Bereich geändert werden, spiegeln die Elemente in der Direktive auch die Änderungen wider.

Wenn Sie andere Attribute oder Objekte aus dem übergeordneten Bereich anzeigen möchten, können Sie dies mit _$watch_ und _$watchCollection_ tun (siehe unten)

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="[email protected]" 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"
  }]
});
_

In der Dokumentation von Angular finden Sie immer detaillierte Erklärungen zu den Anweisungen.

50
Yogesh Manware
 scope: false
 transclude: false

und Sie werden den gleichen Umfang haben (mit übergeordnetem Element)

$scope.$watch(...

Abhängig von den beiden Optionen scope & transclude gibt es viele Möglichkeiten, auf den übergeordneten Bereich zuzugreifen.

11
Stepan Suvorov

Hier ist ein Trick, den ich einmal verwendet habe: Erstellen Sie eine "Dummy" -Direktive, um den übergeordneten Bereich aufzunehmen und ihn außerhalb der gewünschten Direktive zu platzieren. So 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

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

Vielleicht nicht die anmutigste Lösung, aber es hat die Arbeit erledigt.

7
anewcomer

Wenn Sie ES6-Klassen 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(...)
4
Simon H

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

Fügen Sie einfach Folgendes in Ihre Vorlage ein:

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

Es wird nur das übergeordnete Bereichsattribut/die übergeordnete Bereichsvariable geschrieben, auf die Sie zugreifen möchten.

Beachten Sie auch den ; '' am Ende der Anweisung, um sicherzustellen, dass Ihre Vorlage keine Ausgabe enthält. (Angular wertet jede Anweisung aus, gibt jedoch nur die letzte aus.

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

0