6

I'd like to use a directive, transclude content, and call directive's controller method within the transcluded part:

<mydirective>
  <div ng-click='foo()'>
    click me
  </div>
</mydirective>


app.directive "mydirective", ->

  return {
    restrict:  'EACM',
    transclude: true
    template: "<div ng-transclude></div>"
    scope: { } #required: I use two way binding on some variable, but it's not the question here

    controller: [ '$scope', ($scope)->
      $scope.foo = -> console.log('foo')
    ]
  }

plunkr here.

How can I do that please?

Jérôme Beau
  • 10,608
  • 5
  • 48
  • 52
apneadiving
  • 114,565
  • 26
  • 219
  • 213

3 Answers3

6

I have a different answer, which is not a hack and I hope it will be accepted..

see my plunkr for a live demo

Here is my usage of the directive

<div custom-directive custom-name="{{name}}">      
  if transclude works fine you should see my name right here.. [{{customName}}]
</div>

Note I am using customName within the directive and I assign it a value as part of the directive's scope.

Here is my directive definition

angular.module('guy').directive('customDirective', function($compile, $timeout){
    return {
      template : '<div class="custom-template">This is custom template with [{{customName}}]. below should be appended content with binding to isolated scope using the transclude function.. wait 2 seconds to see that binding works</div>',
      restrict: 'AC',
      transclude: true, 
      scope : {
        customName : '@'
      }, 
      link : function postLink( scope, element, attrs, dummy, transcludeFn ){
          transcludeFn( scope, function(clone, innerScope ){
             var compiled = $compile(clone)(scope);
             element.append(compiled);
          });

         $timeout( function(){

            scope.customName = 'this stuff works!!!';

          }, 2000);
      }
    }
  });

Note that I am changing the value on the scope after 2 seconds so it shows the binding works.

After reading a lot online, I understood the following:

  • the ng-transclude directive is the default implementation to transclusion which can be redefined per use-case by the user
  • redefining a transclusion means angular will use your definition on each $digest
  • by default - the transclusion creates a new scope which is not a child of the isolated scope, but rather a sibling (and so the hack works). If you redefine the transclusion process you can choose which scope is used while compiling the transcluded content.. -- even though a new scope is STILL created it seems
  • There is not enough documentation to the transclude function. I didn't even find it in the documentation. I found it in another SO answer
Community
  • 1
  • 1
guy mograbi
  • 27,391
  • 16
  • 83
  • 122
3

This is a bit tricky. The transcluded scope is not the child of the directive scope, instead they are siblings. So in order to access foo from the ng-click of the transcluded element, you have to assign foo to the correct scope, i.e. the sibling of the directive scope. Be sure to access the transcluded scope from the link function because it hasn't been created in controller function.

Demo link

var app = angular.module('plunker', []);
app.directive("mydirective", function(){
  return {
    transclude: true,
    restrict: 'EACM',
    template: "<div> {{ name }} <br/><br/> <div ng-transclude> </div></div>",
    scope: { },
    link: function($scope){
      $scope.name = 'Should change if click below works';
      $scope.$$nextSibling.foo = function(){
        console.log('foo');
        $scope.name = 'it works!';
      }
    }
  }
})

Another way is assigning foo to the parent scope because both prototypally inherits from the parent scope, i.e.

$scope.$parent.foo = ...
Buu
  • 49,745
  • 5
  • 67
  • 85
0

Technically, if you remove scope: { }, then it should work since the directive will not create an isolated scope. (Btw, you need to add restrict: "E", since you use the directive as element)

I think it makes more sense to call actions defined in parent scope from directive rather than call the actions in the directive from parent scope. Directive should be something self-contained and reusable. The actions in the directive should not be accessible from outside.

If you really want to do it, you can try to emit an event by calling $scope.$broadcast(), and add a listener in the directive. Hope it helps.

zs2020
  • 53,766
  • 29
  • 154
  • 219
  • ok for restrict, just made a plunkr. I see your solution works but what if I want to keep the scope? I intend to use the directive several time in the same page + already use it to bind with other variables – apneadiving Aug 30 '13 at 21:24
  • is it possible to give the transcluded element the scope I desire or access the directive scope somehow? – apneadiving Aug 30 '13 at 21:27
  • @apneadiving I think it makes sense to call action defined in parent scope from directive rather than call the action in the directive from parent. Directive should be something self-contained and reusable. The actions should not be accessible from outside. – zs2020 Aug 30 '13 at 21:31