Як отримати доступ до батьківського діапазону з власної директиви * із власним областю * у AngularJS?


327

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

Те, що я дійсно хочу, - це вміти $watchвираз у батьківській області. Якщо я можу це зробити, то я можу досягти того, що намагаюся зробити тут: AngularJS - Як зробити частку зі змінними?

Важлива примітка полягає в тому, що директива повинна бути повторно використана в межах однієї батьківської області. Тому поведінка за замовчуванням (область дії: хибна) для мене не працює. Мені потрібен індивідуальний діапазон на екземпляр директиви, а потім мені потрібна $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'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?
                }
            }
        }
    };
});

Відповіді:


644

Дивіться, які нюанси обсягу прототипічного / прототипного успадкування в AngularJS?

Підводячи підсумок: спосіб доступу директиви до своєї батьківської ( $parent) сфери дії залежить від типу області, яку створює директива:

  1. default ( scope: false) - директива не створює нової області застосування, тому тут немає спадкування. Область застосування директиви така сама, як і батьківська / контейнерна. У функції посилання використовуйте перший параметр (як правило scope).

  2. scope: true- Директива створює нову дочірню область, яка прототипно успадковується від батьківської області. Властивості, визначені в батьківській області, доступні директиві scope(через спадкування прототипів). Тільки остерігайтеся запису в властивість примітивної області застосування - це створить нову властивість в області застосування директиви (що приховує / затінює властивість батьківського діапазону з тим самим іменем).

  3. scope: { ... }- директива створює нову ізоляцію / ізольовану область. Це прототипно не успадковує батьківську область. Ви все одно можете отримати доступ до батьківської області за допомогою $parent, але це зазвичай не рекомендується. Замість цього, ви повинні вказати , який батько сферу властивості (і / або функція) Директива потреба допомогою додаткових атрибутів на той же елемент , де використовується директива, використовуючи =, @і &позначення.

  4. transclude: true- директива створює нову "переключену" дочірню область, яка прототипічно успадковується від батьківської області. Якщо директива також створює ізоляторну область, переключені та ізоляційні сфери є побратимами. $parentВластивість кожної області посилається на той же батьківський обсязі.
    Кутове оновлення v1.3 : Якщо директива також створює область ізоляції, переключена область застосування тепер є дочірньою область ізоляту. Трансклюдовані та ізолювані сфери вже не є побратимами. $parentВластивість сфери в даний час включено через посилання на ізолятів сфері.

Наведене вище посилання містить приклади та зображення всіх 4 типів.

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

Перегляд:

Для 1. і 2. вище: зазвичай ви вказуєте, яке батьківське властивість потребує директиву через атрибут, а потім $ watch:

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

1
ДЯКУЮ, Марку. Виявляється, рішення, яке я розмістив у розділі Як зробити часткове зі змінними, справді працює дуже красиво. Те, що вам насправді потрібно було пов’язати зі мною, - це щось під назвою "Нюанси написання HTML та визнання того, що ваш елемент не вкладений у ng-контролер, як ви вважаєте, що це є". Нічого ... помилка новачка. Але це корисне доповнення до вашої іншої (набагато довшої) відповіді, що пояснює сфери застосування.
colllin

@collin, чудово, я радий, що ти вирішив свою проблему, оскільки я не був зовсім впевнений, як відповісти на твій інший (тепер видалений) коментар.
Марк Райкок

Які речі можна / мені слід виконувати протягомscope.$watch('localName3', function() { ...[?? WHAT TO DO HERE for example?] });
Джунайд Кадір,

1
Чи не @Andy, немає не використовувати $parseз =: скрипкою . $parseпотрібен лише при неізольованих областях.
Марк Райкок

1
Це чудова відповідь, дуже ґрунтовна. Це також ілюструє, чому я просто ненавиджу працювати з AngularJS.
Джон Тришеро

51

Доступ до методу контролера означає доступ до методу в області батьків з області контролера / посилання / області застосування.

Якщо директива ділиться / успадковує батьківський обсяг, тоді просто звернутися до методу батьківського діапазону.

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

Є кілька варіантів (може бути більше, ніж перераховано нижче) для виклику методу батьківського діапазону з ізольованих директив області чи перегляду батьківських змінних областей ( спеціально варіант № 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="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"
    }]

});

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

Варіант №2. Через об'єкт буквально та через посилання / сферу дії директиви

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

робочий 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="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"
    }]
});

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

Варіант №4. Через посилання на функцію та через посилання / область застосування директиви

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

});

робочий 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="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"
    }]
});

робочий 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="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>

скрипт 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 для отримання детальних пояснень щодо директив.


10
Він наполегливо працює над своєю представницею ... так важко для своєї представниці ... він наполегливо працює над своєю представницею, щоб ви краще підтримали його правильно.
струнка

7
знижена - будь-яка цінна інформація у відповіді недоступна через її тривалість
відшкодування

2
Я відповів на запитання з усіма доступними альтернативами з чітким розділенням. На мою думку, короткі відповіді не завжди корисні, доки перед вами не буде велика картина.
Yogesh Manware

@YogeshManware: Це може бути значно скорочено, залишаючи невідповідні речі, такі як таблиці стилів, не використовуючи тривалу розмітку, спрощуючи приклади, щоб не використовувати речі типу "групи по" тощо. Це також було б дуже корисно з якимось поясненням для кожен приклад.
чорт

Це не привід відмовитись від голосування. Люди зловживають цією приватністю
Winnemucca

11
 scope: false
 transclude: false

і у вас буде такий самий обсяг (з батьківським елементом)

$scope.$watch(...

Існує маса способів отримати доступ до батьківської області, залежно від цих двох варіантів та переключення.


Так, короткий і солодкий, і правильний. Вони, схоже, поділяють абсолютно таку ж область застосування, як і батьківський елемент ..., що робить їх неможливим повторне використання в тій же області. jsfiddle.net/collindo/xqytH
colllin

2
багато разів нам потрібен ізольований обсяг, коли ми пишемо багаторазовий компонент, тому рішення не таке просте
Івон Хюн

8

Ось хитрість, яку я використав один раз: створити "фіктивну" директиву, щоб утримувати батьківський обсяг і розміщувати його десь поза бажаною директивою. Щось на зразок:

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>

Можливо, не найграціозніше рішення, але це справи було виконано.


4

Якщо ви використовуєте класи та ControllerAsсинтаксис ES6 , вам потрібно зробити дещо інше.

Дивіться фрагмент нижче та зауважте, що vmце ControllerAsзначення батьківського контролера, як використовується у батьківському HTML

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

        scope.vm.func(...)

0

Спробувавши все, я нарешті придумав рішення.

Просто розмістіть у своєму шаблоні таке:

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

Він просто записує батьківський атрибут / змінна область, до якого потрібно отримати доступ до поточної області.

Також зауважте ; ''в кінці висловлювання, щоб переконатися, що у вашому шаблоні немає результату. (Кутовий оцінює кожне твердження, але виводить лише останнє).

Це трохи хакіт, але після декількох годин спроб і помилок, це робить свою роботу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.