Como acessar o escopo pai a partir de uma diretiva personalizada * com o próprio escopo * no AngularJS?

Eu estou procurando por qualquer maneira de acessar o escopo “pai” dentro de uma diretiva. Qualquer combinação de escopo, transclude, requer, passando variables ​​(ou o próprio escopo) de cima, etc. Eu estou totalmente disposto a me atrasar, mas eu quero evitar algo totalmente hacky ou insustentável. Por exemplo, eu sei que eu poderia fazer isso agora pegando o $scope dos parâmetros preLink e iterando sobre seus escopos $sibling para encontrar o “pai” conceitual.

O que eu realmente quero é poder $watch uma expressão no escopo pai. Se eu posso fazer isso, então eu posso realizar o que estou tentando fazer aqui: AngularJS – Como renderizar uma parcial com variables?

Uma observação importante é que a diretiva deve ser reutilizável dentro do mesmo escopo pai. Portanto, o comportamento padrão (scope: false) não funciona para mim. Eu preciso de um escopo individual por instância da diretiva e, em seguida, preciso $watch uma variável que vive no escopo pai.

Um exemplo de código vale mais que mil palavras, então:

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

Veja Quais são as nuances do escopo protótipo / inheritance prototípica em AngularJS?

Resumindo: a maneira como uma diretiva acessa seu escopo pai ( $parent ) depende do tipo de escopo que a diretiva cria:

  1. default ( scope: false ) – a diretiva não cria um novo escopo, portanto não há inheritance aqui. O escopo da diretiva é o mesmo escopo do pai / contêiner. Na function de link, use o primeiro parâmetro (geralmente o scope ).

  2. scope: true – a diretiva cria um novo escopo filho que herda de forma prototípica do escopo pai. As propriedades definidas no escopo pai estão disponíveis para o scope da diretiva (devido à inheritance prototypal). Apenas tome cuidado ao gravar em uma propriedade de escopo primitivo – isso criará uma nova propriedade no escopo da diretiva (que oculta / obscurece a propriedade de escopo pai do mesmo nome).

  3. scope: { ... } – a diretiva cria um novo escopo isolado / isolado. Ele não herda prototipicamente o escopo pai. Você ainda pode acessar o escopo pai usando $parent , mas isso não é normalmente recomendado. Em vez disso, você deve especificar quais propriedades de escopo pai (e / ou function) a diretiva precisa por meio de atributos adicionais no mesmo elemento em que a diretiva é usada, usando as = , @ e & notation.

  4. transclude: true – a diretiva cria um novo escopo filho “transcluído”, que herda de forma prototípica do escopo pai. Se a diretiva também cria um escopo isolado, os escópios transcluídos e isolados são irmãos. A propriedade $parent de cada escopo referencia o mesmo escopo pai.
    Atualização v1.3 angular : se a diretiva também cria um escopo isolado, o escopo transcluído agora é um filho do escopo isolado. Os escópios transcluídos e isolados não são mais irmãos. A propriedade $parent do escopo transcluded agora faz referência ao escopo isolate.

O link acima tem exemplos e fotos de todos os 4 tipos.

Você não pode acessar o escopo na function de compilation da diretiva (como mencionado aqui: https://github.com/angular/angular.js/wiki/Understanding-Directives ). Você pode acessar o escopo da diretiva na function de link.

Assistindo:

Para 1. e 2. acima: normalmente você especifica qual propriedade pai a diretiva precisa através de um atributo, então $ observe:

 

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

Se você está assistindo uma propriedade de object, você precisará usar $ parse:

 

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

Para 3. acima (isolate scope), observe o nome que você atribuiu à propriedade directive usando a notação @ ou = :

 

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

Acessar o método do controlador significa acessar um método no escopo pai a partir do controlador / link / escopo da diretiva.

Se a diretiva está compartilhando / herdando o escopo pai, é bastante simples simplesmente invocar um método de escopo pai.

Pouco mais trabalho é necessário quando você deseja acessar o método de escopo pai do escopo da diretiva Isolada.

Existem poucas opções (podem ser mais do que listadas abaixo) para invocar um método de escopo pai do escopo de diretivas isoladas ou observar variables ​​de escopo pai (especialmente a opção nº 6 ).

Observe que usei a link function nesses exemplos, mas você também pode usar um directive controller com base no requisito.

Opção 1. Através do literal de object e do modelo de diretiva html

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

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

Opção 2. Através do literal de object e do link / escopo da diretiva

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

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

Opção # 3. Através da referência de function e do modelo de diretiva html

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

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

Opção # 4. Através da referência de function e do link / escopo da diretiva

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

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

Opção # 5: Através de ng-model e bidirecional, você pode atualizar variables ​​de escopo pai. . Portanto, talvez você não precise invocar funções de escopo pai em alguns casos.

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

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

Opção # 6: Através de $watch e $watchCollection É de duas formas de binding para items em todos os exemplos acima, se os itens são modificados no escopo pai, itens na diretiva também refletiria as alterações.

Se você quiser assistir outros atributos ou objects do escopo pai, você pode fazer isso usando $watch e $watchCollection conforme abaixo

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

script app.js

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

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

Você sempre pode consultar a documentação do AngularJs para obter explicações detalhadas sobre diretivas.

  scope: false transclude: false 

e você terá o mesmo escopo (com elemento pai)

 $scope.$watch(... 

Há muitas maneiras de acessar o escopo pai dependendo do escopo e transclude das duas opções.

Aqui está um truque que eu usei uma vez: criar uma diretiva “fictícia” para manter o escopo pai e colocá-lo em algum lugar fora da diretiva desejada. Algo como:

 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 depois

 

Talvez não seja a solução mais graciosa, mas o trabalho foi feito.

Se você estiver usando as classs ES6 e a syntax ControllerAs , precisará fazer algo um pouco diferente.

Veja o trecho abaixo e observe que vm é o valor ControllerAs do ControllerAs pai, conforme usado no HTML pai

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

Tendo tentado tudo, finalmente encontrei uma solução.

Basta colocar o seguinte no seu modelo:

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

Ele apenas grava o atributo / variável de escopo pai que você deseja acessar no escopo atual.

Observe também o ; '' ; '' no final da declaração, é para garantir que não haja saída em seu modelo. (Angular avalia todas as instruções, mas apenas mostra a última).

É um pouco hacky, mas depois de algumas horas de tentativa e erro, faz o trabalho.