Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

transcluded content requires the controller of the transcluding directive

I have a directive which is transcluding it's content. And in the transcluded content is a directive which requires the controller of the transcluding directive. This throws an error if i'm creating a transclude function in the transcluding directive. I think this is because the transcluded content gets cloned when you provide a transclude function (https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L846).

I also have a plunker describing my problem: http://plnkr.co/edit/rRKWW6zfjZuUiw1BY4zs?p=preview

What i want to do is i want to transclude the content and parse all of the transcluded content and then put it in the right place in the DOM and compile it myself. The transcluded content is actually the configuration for my directive.

I've also tried emptying the cloned array i receive in the transcluding function, because i actually don't need the content to be transcluded automatically. I just need to parse it and transclude it manually on a later point in time. Angular doesn't need to do anything with my transcluded content. But this doesn't work because the directives are already identified when the transcluding function is called. So when i empty the array i receive an error here (https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L961).

kind regards,

Daan

like image 555
Daan Poron Avatar asked Apr 28 '14 15:04

Daan Poron


1 Answers

When you use require: "^controller" you are telling Angular that the directive requires controller to be attached to an ancestor DOM element at the time the link function is run.

When you do transclusion without using the ngTransclude directive, your parent directive link function get's passed a transclude method. (You already knew that; this is just for completeness.) This transclude method does the following:

  1. Extracts the content for transclusion
  2. If a cloneAttachFn was passed in, clone the content and call the cloneAttachFn
  3. Calls $compile() to compile and link the content (or cloned content) using the scope supplied by the call to transclude (defaults to a new scope that inherits from the $parent of the directive scope).

If you call transclude, and end up not attaching the content as a descendant of an element with the required controller (or not adding the content to the DOM at all), then the content will have no parent with the required controller. Because it can't find the required controller, you get an error.

In your example, if you use kmBar with require: "^kmFoo", you are restricted to adding the transcluded content to DOM nodes that are descendants of nodes that have kmFoo.

The easiest fix is to go ahead and append it to kmFoo's element for the purposes of $compile() and linking, but immediately detach it.

Detach (as opposed to remove) maintains click handlers, etc, so everything will continue to work if you append the element later. If you are using early versions of AngularJS, you may need to include jQuery to get detach since it wasn't included in early versions of jqLite.

Here's a snippet of a Plunk I put together

app.directive('kmFoo', function() {
  return {
    restrict: 'A',
    scope: true,
    template: '<div></div>',
    transclude: true,
    controller: function() {
      // ...
    },
    link: function(scope, $element, attrs, ctrl, transcludeFn) {
      console.log('linking foo');
      // We are going to temporarily add it to $element so it can be linked,
      //  but after it's linked, we detach it.
      transcludeFn(scope, function(clone) {
        console.log('transcluding foo');
        $element.append(clone);
        c = clone;
      }).detach();// <- Immediately detach it
    }
  };
});

app.directive('kmBar', function() {
  return {
    restrict: 'A',
    scope: true,
    require: '^kmFoo',
    link: function(scope, $element, attrs, fooCtrl) {
      console.log('linking bar');
      // Right now it's a child of the element containing kmFoo,
      //  but it won't be after this method is complete.
      // You can defer adding this element to the DOM
      //  for as long as you want, and you can put it wherever you want.
    }
  };
});
like image 85
Joe Enzminger Avatar answered Nov 12 '22 13:11

Joe Enzminger