Search code examples
angularjstransclusion

Adding $properties to Transcluded Scope


I have a directive with a transcluded scope something like this:

<my-control>
    Some Content: {{value}}
</my-control>

Where value is coming from the parent scope.

I want to add a function that interacts with the control's scope, so I can do stuff like this:

<my-control>
    Some Content: {{value}}
    <button ng-click="$close()">Close</button>
</my-control>

Similar to the way ngRepeat adds properties like $index to the row scope. What is the easiest way to do this in my directive?


Solution

  • When we do not specify either scope:true(new Scope) or scope:{} (isolatedScope) and when we re-use the directive, the properties defined on the scope will be overridden.

    For Ex:

    <div ng-controller="AppCtrl">
        <my-control name="myControl1">
            Some Content: {{value}} 
            My Control Name: {{name}}
        </my-control>
        <my-control name="myControl2">
            Some Content: {{value}} 
            My Control Name: {{name}}
        </my-control>
    </div>
    

    Instead of printing both myControl1 and myControl2 on the screen, it will print myControl2 two times.

    Plnkr

    To overcome this issue try any of the below solutions.

    Solution1

    transclde:true will create a new Scope. set the properties on this scope instead of the directive's scope.

    app.directive('myControl', function() { 
      return {
        restrict: 'E',
        transclude: true,
        template: '<div><p><strong>Welcome to the testMe directive!</strong></p> <div ng-transclude></div></div>',
        link: function(scope, element, attrs) {
          var transclusionTarget = element[0].querySelector('[ng-transclude]').firstChild;
          var transclusionScope = angular.element(transclusionTarget).scope();
          transclusionScope.name = attrs.name;
        }
      }
    });
    

    here the element's under ng-transclude div will be compiled with the transclusionScope, grab it and update the properties in it.

    Plnkr

    Solution2 Instead of using ng-transclude, manually transclude the content.

    app.directive('myControl', function() {  
      return {
        restrict: 'E',
        transclude: true,
        template: '<div><p><strong>Welcome to the testMe directive!</strong></p> <div transclude-target></div></div>',
        link: function(scope, element, attrs, directiveCtrl, transcludeFn ) {
    
          var transclusionScope = scope.$new(),
              transclusionTarget = element[0].querySelector('[transclude-target]');
    
          transclusionScope.name = attrs.name;
    
          transcludeFn(transclusionScope, function (clone) {
            angular.element(transclusionTarget).append(clone);
          });
        }
      }
    });
    

    Here, create a new Scope extending the directive's scope using scope.$new(). And update the properties in it.

    Plnkr

    Solution1 may not work in all the cases. By the time we access firstChild and if it is not ready Solution1 will fail.

    Solution2 is cleaner and will work in all the cases.