Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS directive transclude part binding

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?

like image 621
apneadiving Avatar asked Aug 30 '13 21:08

apneadiving


2 Answers

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
like image 161
guy mograbi Avatar answered Oct 04 '22 16:10

guy mograbi


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 = ...
like image 37
Buu Nguyen Avatar answered Oct 04 '22 15:10

Buu Nguyen