web-dev-qa-db-fra.com

Comment accéder à la portée parent depuis une directive personnalisée * avec sa propre portée * dans AngularJS?

Je cherche une manière quelconque d'accéder à la portée "parent" au sein d'une directive. Toute combinaison d'étendue, de transition, d'exigence, de passer des variables (ou de l'étendue elle-même) d'en haut, etc. Par exemple, je sais que je pourrais le faire maintenant en prenant le $scope à partir des paramètres preLink et en itérant sur ses $sibling étendues pour trouver le "parent" conceptuel.

Ce que je veux vraiment, c'est pouvoir $watch une expression dans la portée parente. Si je peux faire cela, alors je peux accomplir ce que j'essaie de faire ici: AngularJS - Comment rendre un partiel avec des variables?

Une remarque importante est que la directive doit être réutilisable dans le même champ parent. Par conséquent, le comportement par défaut (scope: false) ne fonctionne pas pour moi. J'ai besoin d'une portée individuelle par instance de la directive, puis de $watch une variable qui réside dans la portée parente.

Un exemple de code vaut 1000 mots, donc:

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

Voir Quelles sont les nuances de l'héritage prototypal/prototypique dans AngularJS?

Pour résumer: la manière dont une directive accède à sa portée parent ($parent) dépend du type de portée créée par la directive:

  1. default (scope: false) - la directive ne crée pas de nouvelle portée, il n'y a donc pas d'héritage ici. La portée de la directive est la même que celle du parent/conteneur. Dans la fonction de liaison, utilisez le premier paramètre (généralement scope).

  2. scope: true - la directive crée une nouvelle portée enfant qui hérite de manière prototypique de la portée parent. Les propriétés définies sur la portée parente sont disponibles pour la directive scope (en raison de l'héritage du prototype). Méfiez-vous simplement d'écrire dans une propriété de portée primitive - qui créera une nouvelle propriété sur la portée de la directive (qui masque/masque la propriété de portée parent du même nom).

  3. scope: { ... } - la directive crée une nouvelle portée isolée/isolée. Il n'hérite pas de manière prototypique de la portée parent. Vous pouvez toujours accéder à l'étendue parent à l'aide de $parent, mais cela n'est normalement pas recommandé. Au lieu de cela, vous devez spécifier les propriétés (et/ou fonction) parent (s) requises par la directive via des attributs supplémentaires sur le même élément que celui où elle est utilisée, en utilisant les notations =, @ et &.

  4. transclude: true - la directive crée une nouvelle portée enfant "transcluded", qui hérite de manière prototypique de la portée parent. Si la directive crée également une étendue d'isolement, les étendues transcluded et isolate sont des frères et soeurs. La propriété $parent de chaque portée fait référence à la même portée parent.
    Mise à jour angulaire de la v1.3 : Si la directive crée également une étendue isolée, la portée transclutée est maintenant un enfant de la portée isolate. Les portées transclus et isolées ne sont plus des frères et sœurs. La propriété $parent de la portée transcluded fait maintenant référence à la portée isolate.

Le lien ci-dessus contient des exemples et des images des 4 types.

Vous ne pouvez pas accéder à la portée de la fonction de compilation de la directive (comme indiqué ici: https://github.com/angular/angular.js/wiki/Understanding-Directives ). Vous pouvez accéder à la portée de la directive dans la fonction de lien.

Regarder:

Pour 1. et 2. ci-dessus: normalement, vous spécifiez la propriété parent dont la directive a besoin via un attribut, puis $ regardez-la:

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

Si vous regardez une propriété d'objet, vous devrez utiliser $ parse:

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

Pour 3. ci-dessus (isoler scope), observez le nom que vous donnez à la propriété de directive en utilisant la notation @ ou =:

<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

Accéder à la méthode du contrôleur signifie accéder à une méthode sur la portée parent à partir de la directive contrôleur/lien/portée.

Si la directive partage/hérite de la portée parente, il est assez simple de simplement appeler une méthode de portée parente.

Un peu plus de travail est requis lorsque vous souhaitez accéder à la méthode de la portée parente à partir de la portée de la directive Isolated.

Il existe peu d'options (peut-être plus que celles énumérées ci-dessous) pour invoquer une méthode d'étendue parent à partir de directives isolées ou d'observer des variables d'étendue parent (option # 6 spécialement).

Notez que j'ai utilisé _link function_ dans ces exemples, mais vous pouvez également utiliser un _directive controller_ en fonction des besoins.

Option n ° 1.Modèle littéral d'objet et modèle HTML de directive

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

});
_

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

Option 2. Trajectoire d'objet et de directive lien/portée

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

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

Option n ° 3. Par la référence de la fonction et à partir de la directive modèle 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="[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"
    }]
});
_

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

Option # 4. Via la référence de la fonction et de la directive 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" 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"
    }]

});
_

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

Option n ° 5: via ng-model et une liaison bidirectionnelle, vous pouvez mettre à jour les variables de portée parent.. Il est donc possible que vous n’ayez pas besoin d’appeler les fonctions de portée parent dans certains cas.

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

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

Option 6: jusqu'à _$watch_ et _$watchCollection_ Il s'agit d'une liaison bidirectionnelle pour items dans tous les exemples ci-dessus, si les éléments sont modifiés dans la portée parente, les éléments dans la directive refléterait également les changements.

Si vous souhaitez surveiller d'autres attributs ou objets de la portée parente, vous pouvez le faire en utilisant _$watch_ et _$watchCollection_ comme indiqué ci-dessous.

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

Vous pouvez toujours consulter la documentation AngularJs pour obtenir des explications détaillées sur les directives.

50
Yogesh Manware
 scope: false
 transclude: false

et vous aurez la même portée (avec élément parent)

$scope.$watch(...

Il existe de nombreuses façons d’accéder à la portée parente en fonction de ces deux options portée et transclude.

11
Stepan Suvorov

Voici une astuce que j'ai utilisée une fois: créez une directive "factice" pour conserver la portée parent et placez-la quelque part en dehors de la directive souhaitée. Quelque chose comme:

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

puis

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

Peut-être pas la solution la plus gracieuse, mais le travail a été fait.

7
anewcomer

Si vous utilisez les classes ES6 et la syntaxe ControllerAs, vous devez faire quelque chose de légèrement différent.

Voir l'extrait de code ci-dessous et notez que vm est la valeur ControllerAs du contrôleur parent, telle qu'elle est utilisée dans le code HTML parent.

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

        scope.vm.func(...)
4
Simon H

Après avoir tout essayé, j'ai finalement trouvé une solution.

Placez simplement les éléments suivants dans votre modèle:

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

Il écrit simplement l'attribut/variable d'étendue parent que vous souhaitez accéder à l'étendue actuelle.

Notez également le ; '' à la fin de l'instruction, afin de vous assurer qu'il n'y a pas de sortie dans votre modèle. (Angular évalue chaque instruction, mais ne génère que la dernière).

C'est un peu hacky, mais après quelques heures d'essais et d'erreurs, cela fait le travail.

0
Jeffrey Roosendaal