angularjs - restrict - directive change parent scope variable




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

Eu estou procurando por qualquer maneira de acessar o escopo "pai" dentro de uma diretiva. Qualquer combinação de escopo, transclude, requer, passando variáveis ​​(ou o próprio escopo) de cima, etc. Estou totalmente disposto a me dobrar, mas eu quero evitar algo totalmente hacky ou inatingível. Por exemplo, eu sei que poderia fazer isso agora mesmo 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 variáveis?

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

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 a partir do escopo de diretivas isoladas ou observar variáveis ​​de escopo pai (especialmente a opção nº 6 ).

Note que eu usei a link function nesses exemplos, mas você também pode usar um directive controller baseado em requisitos.

Opção 1. Através do literal de objeto e do modelo de diretiva 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(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"
    }]

});

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

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

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

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

Opção # 3. Através da referência de função e do modelo de diretiva 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"
    }]
});

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

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

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

});

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

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

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

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

Opção # 6: Através de $watch e $watchCollection É de duas formas de ligação 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 a outros atributos ou objetos do escopo pai, você pode fazer isso usando $watch e $watchCollection como dados abaixo

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

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


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

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

Talvez não seja a solução mais graciosa, mas deu conta do recado.


Veja Quais são as nuances do escopo protótipo / herança prototípica no 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á herança aqui. O escopo da diretiva é o mesmo escopo do pai / contêiner. Na função de link, use o primeiro parâmetro (geralmente o scope ).

  2. scope: true - a diretiva cria um novo escopo filho que, de forma prototípica, herda do escopo pai. As propriedades definidas no escopo pai estão disponíveis para o scope da diretiva (devido à herança 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 função) 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 escopos 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 imagens de todos os 4 tipos.

Você não pode acessar o escopo na função de compilação da diretiva (como mencionado aqui: https://github.com/angular/angular.js/wiki/Understanding-Directives ). Você pode acessar o escopo da diretiva na função 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:

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

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

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

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

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 = :

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

Se você estiver usando as classes ES6 e a sintaxe do 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(...)




angularjs-directive