Angular $compile with required controller
Asked Answered
H

3

18

I have a composite list directive - that is - a list item that can be a list himself.
The parent directive defines the controller:

.directive('parent', function() {
    controller: function($scope) {
    },
    link: function (scope, element, attrs) {
    }
})

The list (of items) requires the parent controller which by itself works fine (why shouldn't it..):

.directive('list', function() {
     require: '^parent',
     link: function (scope, element, attrs, parentCtrl) {
     }
  })

The same goes as well for the concrete item, which is also fine:

.directive('item', function() {
    require: '^parent',
    link: function (scope, element, attrs, parentCtrl) {
    }
})

An item may be a composite in which case it creates a "list" himself. This composition is done by $compile (ing) a list item inside the link function:

link: function (scope, element, attrs, parentCtrl) {
      ...
      $compile("<list></list>")(scope)
      ... 
}

Which throws an exception:
Controller 'parent', required by directive 'list', can't be found!

The reason for this is obvious - the $compile function didn't provide the controller and therefore the requirement of 'parent' cannot be resolved.
And so I've tried providing the controller manually:

$compile("<list></list>")(scope, null, {'parent': parentCtrl});

Which doesn't throws an exception but still doesn't provide this controller when needed.

Any idea how to make the $compile function accept an external controllers which should be evaluated as well?

Heraclitus answered 18/2, 2014 at 13:8 Comment(0)
H
9

For future reference, here is the solution:

On the $compile function the required controller can be passed as the transcluded controller:

$compile(template)(scope, undefined, {transcludeControllers: injectedCtrl})

Where the "injectedCtrl" is the object which lists controllers the directive expects, for example if you require: '^dad', then transcludeControllers look like this:

 transcludeControllers: {
        dad: { //name of controller in 'require' statement
          instance: vm //instance of controller
        }
      }

See this example: https://jsfiddle.net/qq4gqn6t/11/


Thats it!

Heraclitus answered 10/2, 2015 at 16:32 Comment(2)
Hi, do you have a working plunkr or something? I'm trying to do the same, but the injectedCtrl does not reach the subitem templates...Szabadka
This answer works but not complete... look at the jsfiddle in this one: #34850145Charil
E
23

I've just had a similar issue, and the solution seems to be to first add the element to the parent, and then compile it.

.directive('item', function($compile) {
  return {
    template:'<li><a ng-click="addSubList()">Create Another List</a></li>',
    require: '^parent',
    replace: true,
    link: function(scope, element, attrs, parentCtrl) {

      scope.addSubList = function() {
        var sublist = angular.element('<ul list>');
        element.find('a').append(sublist);
        $compile(sublist)(scope);
      };

    }
  };
});

See this Plunker: http://plnkr.co/edit/dASASrFbtXSMCRZKRAj5?p=preview

Excerpta answered 22/3, 2014 at 21:33 Comment(3)
@Excerpta Thanks a lot! Was having exactly the same issue and I didn't even realize I could append uncompiled node and then let angular compile it!Cloche
note to my future self: remember that the subject to be compiled has to be "wrapped" in angular.element first. passing raw html works when first compiling then appending, but not the other way.Coeducation
Nice solution! It makes sense that it works because if it's not attached to the existing dom before compiling, Angular cannot find the required controllers since the new element is just a "free" element without parent.Detonator
H
9

For future reference, here is the solution:

On the $compile function the required controller can be passed as the transcluded controller:

$compile(template)(scope, undefined, {transcludeControllers: injectedCtrl})

Where the "injectedCtrl" is the object which lists controllers the directive expects, for example if you require: '^dad', then transcludeControllers look like this:

 transcludeControllers: {
        dad: { //name of controller in 'require' statement
          instance: vm //instance of controller
        }
      }

See this example: https://jsfiddle.net/qq4gqn6t/11/


Thats it!

Heraclitus answered 10/2, 2015 at 16:32 Comment(2)
Hi, do you have a working plunkr or something? I'm trying to do the same, but the injectedCtrl does not reach the subitem templates...Szabadka
This answer works but not complete... look at the jsfiddle in this one: #34850145Charil
G
-3
$compile(angular.element("< list>< /list >"))(scope)
Grafting answered 14/4, 2016 at 9:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.