Как получить доступ к родительской области из пользовательской директивы * с помощью собственной области * в AngularJS?

Я ищу любой способ доступа к "родительской" области действия в директиве. Любая комбинация сферы действия, переключение, необходимость, передача в переменных (или сама область) сверху и т.д. Я полностью готов наклониться назад, но я хочу избежать чего-то совершенно взломанного или недостижимого. Например, я знаю, что могу сделать это прямо сейчас, взяв $scope из параметров preLink и итерации по ним $sibling для поиска концептуального "родителя".

Я действительно хочу, чтобы $watch выражение в родительской области. Если я смогу это сделать, тогда я смогу выполнить то, что я пытаюсь сделать здесь: AngularJS - Как отобразить частичное с переменными?

Важное примечание заключается в том, что директива должна быть повторно использована в пределах одной и той же родительской области. Поэтому поведение по умолчанию (scope: false) не работает для меня. Мне нужна отдельная область действия на экземпляр директивы, а затем мне нужно $watch переменную, которая живет в родительской области.

Пример кода стоит 1000 слов, поэтому:

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

Ответ 1

См. Каковы нюансы объема прототипа/прототипного наследования в AngularJS?

Подводя итог: способ, которым директива обращается к своей родительской ($parent) области, зависит от типа области действия, создаваемой директивой:

  • default (scope: false) - директива не создает новую область, поэтому здесь нет наследования. Область действия директивы - та же область действия, что и родительский/контейнер. В функции связи используйте первый параметр (обычно scope).

  • scope: true - директива создает новую дочернюю область, которая прототипически наследуется от родительской области. Свойства, определенные в родительской области, доступны для директивы scope (из-за прототипального наследования). Просто остерегайтесь писать в свойство примитивной области видимости - это создаст новое свойство в области директивы (которое скрывает/затеняет свойство родительской области с тем же именем).

  • scope: { ... } - директива создает новую изоляцию/изолированную область. Он не прототипически наследует родительскую область. Вы все равно можете получить доступ к родительской области с помощью $parent, но это обычно не рекомендуется. Вместо этого вы должны указать свойства родительской области (и/или функции), необходимые директиве, с помощью дополнительных атрибутов в том же элементе, где используется директива, с использованием обозначений =, @ и &.

  • transclude: true - директива создает новую "трансключенную" дочернюю область, которая прототипически наследуется от родительской области. Если в директиве также создается область изоляции, то трансифицированные и изоляционные области являются братьями и сестрами. Свойство $parent для каждой области действия ссылается на одну и ту же родительскую область.
    Angular v1.3 update. Если директива также создает область изоляции, то заочная область теперь является дочерней изолировать область. Заключенные и изолирующие области больше не являются братьями и сестрами. Свойство $parent переводимой области теперь ссылается на область выделения.

В приведенной выше ссылке приведены примеры и изображения всех 4 типов.

Вы не можете получить доступ к области действия в функции компиляции директивы (как указано здесь: https://github.com/angular/angular.js/wiki/Understanding-Directives). Вы можете получить доступ к области директивы в функции ссылок.

Наблюдение:

Для 1. и 2. выше: обычно вы указываете, какое родительское свойство требуется директиве через атрибут, затем $watch it:

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

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

Если вы просматриваете свойство объекта, вам нужно использовать $parse:

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

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

Для 3. выше (выделить область), посмотрите имя, которое вы даете свойству директивы, с помощью обозначения @ или =:

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

Ответ 2

Доступ к методу контроллера означает доступ к методу в родительской области из директивного контроллера/ссылки/области.

Если директива разделяет/наследует родительскую область, то довольно просто просто вызвать метод родительской области.

Требуется немного больше работы, если вы хотите получить доступ к методу родительской области видимости из изолированной области действия.

Существует несколько вариантов (может быть больше, чем указано ниже) для вызова метода родительской области из изолированной области видимости или просмотра переменных родительской области (особенно вариант # 6).

Обратите внимание, что в этих примерах я использовал link function, но вы также можете использовать directive controller на основе требования.

Вариант № 1. Через литерал объекта и шаблон директивы 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"
    }]

});

рабочий plnkr: http://plnkr.co/edit/rgKUsYGDo9O3tewL6xgr?p=preview

Вариант № 2. Через Object literal и из директивы 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

Вариант № 3. Через ссылку на функцию и шаблон 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"
    }]
});

рабочий plnkr: http://plnkr.co/edit/Jo6FcYfVXCCg3vH42BIz?p=preview

Вариант № 4. Через ссылку на функцию и из директивы 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"
    }]

});

рабочий plnkr: http://plnkr.co/edit/BSqx2J1yCY86IJwAnQF1?p=preview

Вариант № 5: через ng-модель и двустороннюю привязку вы можете обновить переменные родительской области.. Таким образом, в некоторых случаях вам может не потребоваться вызывать функции родительской области.

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

Вариант № 6: через $watch и $watchCollection Это двухсторонняя привязка для items во всех вышеприведенных примерах, если элементы изменены в родительской области, элементы в директиве также отражают изменения.

Если вы хотите смотреть другие атрибуты или объекты из родительской области, вы можете сделать это с помощью $watch и $watchCollection, как указано ниже

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

Вы можете всегда ссылаться на документацию AngularJs для подробного объяснения директив.

Ответ 3

 scope: false
 transclude: false

и вы будете иметь ту же область действия (с родительским элементом)

$scope.$watch(...

Существует множество способов доступа к родительской области в зависимости от этих двух возможностей scope & transclude.

Ответ 4

Вот трюк, который я использовал один раз: создайте директиву "dummy" для хранения родительской области и поместите ее где-то вне желаемой директивы. Что-то вроде:

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

а затем

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

Возможно, это не самое изящное решение, но оно выполнило свою работу.

Ответ 5

Если вы используете классы ES6 и синтаксис ControllerAs, вам нужно сделать что-то немного другое.

См. ниже фрагмент и обратите внимание, что vm - это значение ControllerAs родительского контроллера, используемое в родительском HTML

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

        scope.vm.func(...)

Ответ 6

Попробовав все, я наконец придумал решение.

Просто разместите в своем шаблоне следующее:

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

Он просто пишет атрибут/переменную родительской области, к которой вы хотите получить доступ к текущей области.

Также обратите внимание на ; '' в конце инструкции, чтобы убедиться, что в вашем шаблоне нет вывода. (Angular оценивает каждый оператор, но выводит только последний).

Это немного взломанный, но после нескольких часов проб и ошибок он выполняет эту работу.