Может ли несколько директив для одного элемента разделять изолированную область?

Две директивы одного и того же элемента не могут иметь изолированную область видимости, но могут ли они использовать одну и ту же область, изолированную от родителя? И могут ли они использовать свойства, связанные с изолированной областью?

Например, если у меня есть две директивы для элемента

<eDirective aDirective prop="parentProp"/>

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

App.directive('eDirective', function() {
  return {
    restrict: 'E',
    scope: {
      localProp: '=prop'
    },
    ...
  };
});

Получает ли другая директива эту область и может ли она использовать свойство bound?

App.directive('aDirective', function() {
  return {
    restrict: 'A',
    link: function postLink(scope, element, attrs) {
        scope.$watch('localProp', function(newProp, oldProp) {
          ...
        }
    },
    ...
  };
});

Моя первоначальная попытка (в значительной степени закодированная, как указано выше) не удалась.

Ответы

Ответ 1

Я предлагаю вам использовать связь между контроллерами директив через свойство require вторичной директивы. Первая директива (e-директива) содержит изолированную область действия, тогда как вторая вспомогательная директива (a-директива) имеет ссылку на первую директиву и задает свойства через функции, определенные в первой директиве. Небольшой образец был бы (см. Plunker):

<!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://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js" data-semver="1.2.16"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <div e-directive config="parentConfig" a-directive></div>
  </body>

</html>

и javascript:

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

app.controller('MainCtrl', function($scope) {
  $scope.parentConfig = {};
});

app.controller('ECtrl', function ( $scope ) {
  this.setProp = function(newProp){$scope.config.prop = newProp;};

  $scope.$watch('config', function(newProp, oldProp) {
    console.log(oldProp, newProp);
  });
});

app.directive('eDirective', function() {
  return {
    restrict: 'A',
    scope: {
      config: '='
    },
    controller: 'ECtrl',
    link: function(scope, element, attrs) {
      scope.config.prop ="abc";
    }
  };
});

app.directive('aDirective', function() {
  return {
    restrict: 'A',
    require: 'eDirective',
    link: function(scope, element, attrs,ctrl) {
        ctrl.setProp("def");
    }

  };
});

Ответ 2

Вместо изолированной области действия директивы могут создавать новую дочернюю область, которая будет разделяться обеими директивами. Если вам нужно изменить parentProp в директиве, введите и используйте $parse:

<div ng-controller="MyCtrl">
  <e-directive a-directive prop="parentProp"></e-directive>
</div>

JavaScript:

var app = angular.module('myApp', []);
app.controller('MyCtrl', function($scope) {
    $scope.parentProp = { prop1: 'value1' };
});
app.directive('eDirective', function($parse) {
  return {
    restrict: 'E',
    scope: true,
    template: '<div>dir template: {{eDirLocalProp}}<br>'
          + '<a href ng-click="eDirChange()">change</a></div>',
    link: function(scope, element, attrs) {
      scope.eDirProp1     = 'dirPropValue';
      var model           = $parse(attrs.prop);
      scope.eDirLocalProp = model(scope);
      scope.eDirChange    = function() {
          scope.eDirLocalProp.prop1 = "new value";
      };
    }
  };
});
app.directive('aDirective', function() {
  return {
    scope: true,
    link: function postLink(scope, element, attrs) {
      scope.$watchCollection(attrs.prop, function(newValue) {
        console.log('aDirective', newValue);
      });
    },
  };
});

fiddle

Если обе директивы должны создавать свойства в новой дочерней области, используйте какое-то соглашение об именах для предотвращения конфликтов имен. Например, scope.eDirProp1 = ... и scope.aDirProp1 = ....

Ответ 3

Да, например, используя element.isolateScope() (или см. fiddle):

HTML

<div ng-app="app" ng-controller="BaseController as baseCtrl">
  <input type="text" ng-model="inputA.value" directive-config="{data: 'bar'}" >
  <input type="text" ng-model="inputB.value" directive-config="{parsers: externalParser, data: 'buzz'}" custom-input >

  <br><br>
  <span style="font-style: italic; font-size: 12px; color: red;">*Open Console to view output</span>
</div>

JS

    (function(angular){
  "use strict";
  angular.module("app", [])

  .controller("BaseController", ['$scope', function($scope){
    $scope.inputA = {value: "This is inputA"};
    $scope.inputB = {value: "This is inputB"};

    $scope.externalParser = function(value) {
      console.log("...parsing value: ", value);
    }
  }])

  .directive("input", [function() {
    return {
      restrict: "E",
      require: '?ngModel',
      scope: {
        directiveConfig: "="
      },
      link: function(scope, element, attrs, ngModelCtrl) {
        console.log("input directive - scope: ", scope);
        console.log("input directive - scope.directiveConfig.data: ", scope.directiveConfig.data);
      }
    }
  }])

  .directive("customInput", [function() {
    return {
      restrict: "A",
      require: '?ngModel',
      link: function(scope, element, attrs, ngModelCtrl) {
        console.log("");
        console.log("--------------------------------------------");
        console.log("customInput directive - scope: ", scope);

        // Use `element.isolateScope()`
        var parentScope = element.isolateScope();
        console.log("customInput directive - parentScope.directiveConfig.parsers: ", parentScope.directiveConfig.parsers);
        console.log("customInput directive - parentScope.directiveConfig.data: ", parentScope.directiveConfig.data);

        console.log("");
        console.log("--------------------------------------------");
        console.warn("DO NOT USE `$$childHead` as it may not target the element you are expecting; use `element.isolateScope()` instead.");
        // DO NOT USE `$$childHead` as it may not be the element you expect
        console.log("customInput directive - scope.$$childHead.directiveConfig.parsers: ", scope.$$childHead.directiveConfig.parsers);
        console.log("customInput directive - scope.$$childHead.directiveConfig.data: ", scope.$$childHead.directiveConfig.data);
      }
    }
  }])

  ;
})(angular)

вывод консоли

//input directive - scope:  n {$id: 3, $$childTail: null, $$childHead: null, $$prevSibling: null, $$nextSibling: null…}
//input directive - scope.directiveConfig.data:  bar
//input directive - scope:  n {$id: 4, $$childTail: null, $$childHead: null, $$prevSibling: n, $$nextSibling: null…}
//input directive - scope.directiveConfig.data:  buzz

//--------------------------------------------
//customInput directive - scope:  b {$$childTail: n, $$childHead: n, $$nextSibling: null, $$watchers: Array[4], $$listeners: Object…}
//customInput directive - parentScope.directiveConfig.parsers:  function (value) {
//          console.log("...parsing value: ", value);
//        }
//customInput directive - parentScope.directiveConfig.data:  buzz

//--------------------------------------------
//DO NOT USE `$$childHead` as it may not target the element you are expecting; use `element.isolateScope()` instead.
//customInput directive - scope.$$childHead.directiveConfig.parsers:  undefined
//customInput directive - scope.$$childHead.directiveConfig.data:  bar