Can multiple directives for one element share an i

2020-02-27 18:10发布

问题:

Two directives on the same element can not both have isolated scope, but can they both use the same scope isolated from their parent? And can they both use properties bound to the isolated scope?

For example, if I have two directives on an element

<e-directive a-directive prop="parentProp"/>

And one directive defines an isolated scope with a bound property

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

Does the other directive get that scope and can it use the bound property?

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

My initial attempt (pretty much coded as above) failed.

回答1:

I suggest you make use of communicating between the directives' controllers via the require property of the secondary directive. The first directive (e-directive) holds the isolated scope, while the second helper directive (a-directive) has a reference to the first directive and sets properties via functions defined on the first directive. A small sample would be (see 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="angular.js@1.2.x" 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>

and the 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:

Instead of an isolate scope, the directives can create a new child scope, which will be shared by both directives. If you need to modify parentProp in a directive, inject and use $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

If both directives need to create properties on the new child scope, use some kind of naming convention to prevent name clashes. E.g., scope.eDirProp1 = ... and scope.aDirProp1 = ....



回答3:

Yes by using element.isolateScope() for example (or see 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)

console output

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