Come accedere all'ambito padre all'interno di una direttiva personalizzata * con il proprio ambito * in AngularJS?


327

Sto cercando qualsiasi modo per accedere all'ambito "genitore" all'interno di una direttiva. Qualsiasi combinazione di scope, transclude, richiede, passando variabili (o lo scope stesso) dall'alto, ecc. Sono totalmente disposto a piegarmi all'indietro, ma voglio evitare qualcosa di totalmente confuso o non mantenibile. Ad esempio, so che potrei farlo in questo momento prendendo i $scopeparametri preLink e ripetendo i suoi $siblingambiti per trovare il "genitore" concettuale.

Quello che voglio davvero è essere in grado di $watchun'espressione nell'ambito genitore. Se posso farlo, allora posso realizzare quello che sto cercando di fare qui: AngularJS - Come rendere un parziale con variabili?

Una nota importante è che la direttiva deve essere riutilizzabile nello stesso ambito padre. Pertanto il comportamento predefinito (scope: false) non funziona per me. Ho bisogno di un ambito individuale per istanza della direttiva e quindi ho bisogno di $watchuna variabile che risieda nell'ambito genitore.

Un esempio di codice vale 1000 parole, quindi:

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

Risposte:


644

Vedi Quali sono le sfumature dell'ambito prototipo / eredità prototipica in AngularJS?

Riassumendo: il modo in cui una direttiva accede al suo ambito parent ( $parent) dipende dal tipo di ambito che la direttiva crea:

  1. default ( scope: false) - la direttiva non crea un nuovo ambito, quindi qui non c'è ereditarietà. L'ambito di applicazione della direttiva è lo stesso ambito del padre / contenitore. Nella funzione di collegamento, utilizzare il primo parametro (in genere scope).

  2. scope: true- la direttiva crea un nuovo ambito figlio che eredita prototipicamente dall'ambito padre. Le proprietà definite nell'ambito di appartenenza sono disponibili per la direttiva scope(a causa dell'ereditarietà del prototipo). Basta fare attenzione a scrivere su una proprietà dell'ambito primitivo - che creerà una nuova proprietà nell'ambito della direttiva (che nasconde / ombreggia la proprietà dell'ambito padre con lo stesso nome).

  3. scope: { ... }- la direttiva crea un nuovo ambito isolato / isolato. Non eredita prototipicamente l'ambito padre. Puoi comunque accedere all'ambito padre usando $parent, ma questo non è normalmente raccomandato. Invece, si dovrebbe specificare quali portata proprietà genitore (e / o la funzione) le esigenze direttiva attraverso attributi aggiuntivi sullo stesso elemento in cui viene utilizzata la direttiva, con il =, @e &la notazione.

  4. transclude: true- la direttiva crea un nuovo ambito figlio "escluso", che eredita prototipicamente dall'ambito padre. Se la direttiva crea anche un ambito isolato, gli ambiti transcluso e isolato sono fratelli. La $parentproprietà di ciascun ambito fa riferimento allo stesso ambito padre.
    Aggiornamento angolare v1.3 : se la direttiva crea anche un ambito isolato, l'ambito escluso è ora figlio dell'ambito isolato. Gli ambiti esclusi e isolati non sono più fratelli. La $parentproprietà dell'ambito escluso ora fa riferimento all'ambito isolato.

Il link sopra mostra esempi e immagini di tutti e 4 i tipi.

Non è possibile accedere all'ambito nella funzione di compilazione della direttiva (come menzionato qui: https://github.com/angular/angular.js/wiki/Understanding-Directives ). È possibile accedere all'ambito della direttiva nella funzione di collegamento.

Guardando:

Per 1. e 2. sopra: normalmente specifichi quale proprietà parent la direttiva necessita tramite un attributo, quindi $ guardala:

<div my-dir attr1="prop1"></div>

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

Se stai guardando una proprietà di un oggetto, dovrai usare $ parse:

<div my-dir attr2="obj.prop2"></div>

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

Per 3. sopra (isolare l'ambito), guarda il nome che dai alla proprietà direttiva usando la notazione @o =:

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

1
GRAZIE, Mark. Si scopre che la soluzione che ho pubblicato su Come rendere un parziale con variabili funziona davvero molto bene. Ciò a cui avevi davvero bisogno di collegarmi era qualcosa intitolato "Le sfumature di scrivere HTML e riconoscere che il tuo elemento non è nidificato all'interno del controller ng che pensi che sia." Wow ... errore da principiante. Ma questa è un'utile aggiunta alla tua altra risposta (molto più lunga) che spiega gli ambiti.
Colllin,

@collin, fantastico, sono contento che tu abbia risolto il tuo problema, dal momento che non ero abbastanza sicuro di come rispondere al tuo altro commento (ora eliminato).
Mark Rajcok,

Quali cose posso / dovrei esibire all'internoscope.$watch('localName3', function() { ...[?? WHAT TO DO HERE for example?] });
Junaid Qadir,

1
@Andy, no non usare $parsecon =: violino . $parseè necessario solo con ambiti non isolati.
Mark Rajcok,

1
Questa è un'ottima risposta, molto approfondita. Illustra anche perché odio semplicemente lavorare con AngularJS.
John Trichereau,

51

Accedere al metodo controller significa accedere a un metodo su ambito padre dal controller / collegamento / ambito direttiva.

Se la direttiva condivide / eredita l'ambito padre, è abbastanza semplice invocare un metodo ambito padre.

È necessario un po 'più di lavoro quando si desidera accedere al metodo dell'ambito padre dall'ambito della direttiva Isolata.

Esistono poche opzioni (che possono essere più di quelle elencate di seguito) per invocare un metodo dell'ambito padre dall'ambito delle direttive isolato o guardare le variabili dell'ambito padre ( opzione n. 6 in particolare).

Si noti che ho usato link functionin questi esempi ma è possibile utilizzare anche uno directive controllerbasato sul requisito.

Opzione 1. Tramite Object template letterale e direttiva 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"
    }]

});

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

Opzione 2. Attraverso Object letteral e dal link / scope della direttiva

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

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

Opzione # 3. Tramite riferimento alle funzioni e dal modello html della direttiva

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

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

Opzione # 4. Attraverso il riferimento alle funzioni e dal collegamento / ambito della direttiva

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

});

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

Opzione n. 5: tramite ng-model e associazione a due vie, è possibile aggiornare le variabili dell'ambito padre. . Pertanto, in alcuni casi potrebbe non essere necessario richiamare le funzioni dell'ambito padre.

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

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

Opzione n. 6: Through $watche$watchCollection È vincolante itemsin due modi per tutti gli esempi precedenti, se gli articoli vengono modificati nell'ambito genitore, anche gli articoli nella direttiva rifletterebbero le modifiche.

Se si desidera guardare altri attributi o oggetti dall'ambito padre, è possibile farlo utilizzando $watche $watchCollectioncome indicato di seguito

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>

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

Puoi sempre fare riferimento alla documentazione di AngularJs per spiegazioni dettagliate sulle direttive.


10
Lavora sodo per il suo rappresentante ... così duro per il suo rappresentante ... lavora sodo per il suo rappresentante, quindi è meglio che lo voti bene.
magro,

7
declassato - qualsiasi informazione preziosa all'interno della risposta è inaccessibile a causa della sua lunghezza
riparazione

2
Ho risposto alla domanda con tutte le alternative disponibili con una chiara separazione. A mio avviso, le risposte brevi non sono sempre utili fino a quando non hai una grande immagine di fronte a te.
Yogesh Manware

@YogeshManware: potrebbe essere abbreviato molto tralasciando le cose irrilevanti come i fogli di stile, non usando un lungo markup, semplificando gli esempi per non usare cose come "raggruppa per", ecc. Sarebbe anche molto utile con una sorta di spiegazione per ogni esempio.
maledetto

Questo non è un motivo per votare male. La gente abusa di questo privilegio
Winnemucca il

11
 scope: false
 transclude: false

e avrai lo stesso ambito (con elemento genitore)

$scope.$watch(...

Esistono molti modi per accedere all'ambito padre in base a questo ambito di due opzioni e alla chiusura.


Sì, breve, dolce e corretto. Sembrano condividere lo stesso identico ambito dell'elemento genitore però ... il che rende impossibile riutilizzarli nello stesso ambito. jsfiddle.net/collindo/xqytH
colllin il

2
molte volte abbiamo bisogno di un ambito isolato quando scriviamo componenti riutilizzabili, quindi la soluzione non è così semplice
Yvon Huynh

8

Ecco un trucco che ho usato una volta: creare una direttiva "fittizia" per contenere l'ambito padre e posizionarlo da qualche parte al di fuori della direttiva desiderata. Qualcosa di simile a:

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

e poi

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

Forse non è la soluzione più aggraziata, ma ha fatto il lavoro.


4

Se stai usando le classi e la ControllerAssintassi ES6 , devi fare qualcosa di leggermente diverso.

Vedi lo snippet di seguito e nota che vmè il ControllerAsvalore del controller principale utilizzato nell'HTML principale

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

        scope.vm.func(...)

0

Dopo aver provato tutto, ho finalmente trovato una soluzione.

Inserisci quanto segue nel tuo modello:

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

Scrive solo l'attributo / la variabile dell'ambito padre a cui si desidera accedere all'ambito corrente.

Si noti inoltre che ; ''alla fine dell'istruzione, è per assicurarsi che non ci sia output nel modello. (Angular valuta ogni istruzione, ma restituisce solo l'ultima).

È un po 'confuso, ma dopo alcune ore di tentativi ed errori, fa il lavoro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.