11

Here is my script:

angular.module('MyApp',[])
.directive('mySalutation',function(){
    return {
        restrict:'E',
        scope:true,
        replace:true,
        transclude:true,
        template:'<div>Hello<div ng-transclude></div></div>',
        link:function($scope,$element,$attrs){
        }
    };
})
.controller('SalutationController',['$scope',function($scope){
    $scope.target = "StackOverflow";
}])

and the html:

<body ng-app="MyApp">
    <my-salutation ng-controller="SalutationController">
        <strong>{{target}}</strong>        
    </my-salutation>
</body>

The problem is , when SalutationController is applied on my-salutation directive, $scope.target is not visible for transcluded element.But if I put ng-controller on <body> or on <strong> element, it works. As docs says, ng-controller creates new scope.

  • Who can explain, how that scope and the scope of the directive are interfering with each other in this case?

  • How can I put controller on directive? Any hints will be appreciated.

John Slegers
  • 45,213
  • 22
  • 199
  • 169
Engineer
  • 47,849
  • 12
  • 88
  • 91
  • Is this necessary? Directives can take a `controller` parameter in the object, in addition to the `link` parameter. –  Nov 27 '14 at 19:29
  • @jedd.ahyoung Yes, you can add controller option on directive, but in that case you will have same controller for all directive instances. What if I do not want any controller for my directive, or I need another controller?! – Engineer Nov 28 '14 at 09:57

2 Answers2

9

1) The problem is ng-transclude's scope is the sibling scope of your directive. When you put the ng-controller to a parent element, the scope created by ng-controller is parent scope of both your directive and ng-transclude. Due to scope inheritance, the transcluded element is able to bind the {{target}} correctly.

2) You could do that using custom transclusion to bind the scope yourself

.directive('mySalutation',function(){
    return {
        restrict:'E',
        scope:true,
        replace:true,
        transclude:true,
        template:'<div>Hello<div class="transclude"></div></div>',
        compile: function (element, attr, linker) {
            return function (scope, element, attr) {
                linker(scope, function(clone){
                       element.find(".transclude").append(clone); // add to DOM
                });

            };
        }
    };
})

DEMO

Or using the transclude function in the link function:

.directive('mySalutation',function(){
    return {
        restrict:'E',
        scope:true,
        replace:true,
        transclude:true,
        template:'<div>Hello<div class="transclude"></div></div>',
        link: function (scope, element, attr,controller, linker) {
           linker(scope, function(clone){
                  element.find(".transclude").append(clone); // add to DOM
           });
        }
    };
})

DEMO

Khanh TO
  • 48,509
  • 13
  • 99
  • 115
  • [Doc states](http://docs.angularjs.org/api/ng/service/$compile): "Note: The transclude function that is passed to the compile function is deprecated, as it e.g. does not know about the right outer scope. Please use the transclude function that is passed to the link function instead.". This is new in Angular 1.2, as per [this post](http://www.bennadel.com/blog/2561-Changes-In-Transclude-Function-Availability-In-AngularJS-1-2.htm). – kamituel Mar 22 '14 at 09:18
  • @kamituel: Thanks, I did not know it has changed. I have updated the answer (The idea is the same) – Khanh TO Mar 22 '14 at 09:28
  • If I understood your first point correctly, that means if I'll not create new scope for directive,that is `scope` is set to `false`, then the code should work. Why it does not work in that case? – Engineer Mar 22 '14 at 09:29
  • @Engineer: When I debugged. I noticed that there is no difference with `true`, `false`. In both cases, the directive scope is the controller scope (same $id). I think angular does not create a scope if there is already one even if `scope:true` – Khanh TO Mar 22 '14 at 09:33
  • Note that find() only supports tag names, so you may need to do angular.element(element[0].querySelector('.classname')).append(clone). Also, if you are using an isolated scope, you may need to do linker(scope.$parent, ...) – devdigital Jan 01 '15 at 16:16
  • @devdigital: that would not be a problem if we include jQuery on the page. – Khanh TO Jan 02 '15 at 07:03
2

To have the same scope for the directive, and the controller, you can invoke the transcludeFn manually:

angular.module('MyApp',[])
.directive('mySalutation',function(){
    return {
        restrict:'E',
        scope:true,
        replace:true,
        transclude:true,
        template:'<div>Hello<div class="trans"></div></div>',
        link:function(scope, tElement, iAttrs, controller, transcludeFn){
                console.log(scope.$id);
                transcludeFn(scope, function cloneConnectFn(cElement) {
                    tElement.after(cElement);
                }); 
        }
    };
})
.controller('SalutationController',['$scope',function($scope){
    console.log($scope.$id);
    $scope.target = "StackOverflow";
}]);

plunk

You can see that '003' is logged out every time, and your code works as expected with this minor adjustment.

Oleg Belousov
  • 9,981
  • 14
  • 72
  • 127