3

In my understanding, $compile should be able to support nested directive compilation/link, but we came across an issue that the compilation/link is incomplete - only the outermost directive got rendered into DOM, and the issue only reproduced when both below conditions are true:

  1. The inner directive template is loaded via templateUrl (e.g. async manner)
  2. The compilation is triggered outside Angular context.

I wrote a jsfiddler to demo it, part code listed below, for complete case http://jsfiddle.net/pattern/7KjWP/

myApp.directive('plTest', function($compile){
return {
    restrict :'A',
    scope: {},
    replace: true,
    template: '<div>plTest rendered </div>',
    link: function (scope, element){
        $('#button1').on('click', function(){
            var ele;
            ele = $compile('<div pl-shared />')(scope); 
            console.log('plTest compile got : '+ ele[0].outerHTML);
           // scope.$apply();
            element.append(ele);
        });
     }
};
});

myApp.directive('plShared', function($compile, $timeout){
return {
    restrict: 'A',
    scope: {},
    replace: true,
    link: function (scope, element){
        // comment out below line to make render success
        //$timeout(function(){});
        var el = $compile('<div pl-item></div>')(scope);
        console.log('plShared compile got:' + el[0].outerHTML);
        element.append(el);
    }
};
});

myApp.directive('plItem', function($timeout){
return {
    restrict: 'A',
    scope:{},
    template:'<div>plItem rendered <div pl-avatar/></div>',
    link: function(scope){            

    }
};
});

myApp.directive('plAvatar', function(){
return {
    restrict: 'A',
    scope: {}
   , templateUrl: 'avatar.html'
  //  ,template: 'content of avatar.html <div pl-image></div>'
};
});

Interestingly, i can workaround the issue by either calling scope.$apply() somewhere after compile() call (line 27) or adding $timeout(function(){}) call into the link func of one of inner directive (line 41). Is this a defect or by design?

5
  • That's how 2-way binding works, either you make changes inside Angular context, or you have to call $apply() yourself. Commented Aug 23, 2013 at 7:02
  • As @stevuu explained it, the actual problem in your code is this line: ` $('#button1').on('click',..` - angular doesn't know that this event was handled by your code unless you call $scope.$apply() Commented Aug 23, 2013 at 7:30
  • I know it is necessary to call scope.$apply() outside angular context in order to rolling out $evalAsyc and $watch checks, but the things confusing me are why directive compilation has something to do with those checks. A guess may be the templates are loaded in async manner and in that way angular may have no chance to linking all templates before all asyc load finish, hence need the $apply() to explicitly resolve those loading promises from $evalAsyc queue. If that is true, then it is reasonable to have the extra $apply(), otherwise the inconsistency is a bit annoying. Commented Aug 23, 2013 at 13:48
  • BTW, just find from angular sourcecode, $timeout(function{}) also trigger a $rootScope.$apply() call internally. the call is being triggered by default. If i uses $timeout(function{}, 0, false) to skip the call. The second $timeout solution wont work. Commented Aug 23, 2013 at 13:51
  • I had the same issue, and it was also solved by adding $apply, so thank for that information. However I'm still puzzled, because in my case the HTML is always loaded asynchronously and the problem only appears in one situation - where the HTML was previously loaded and is .removed just prior to the $compile and reinsertion. I would have thought that $compile would always be recursive and why need $apply after $compile? Commented May 20, 2014 at 23:18

2 Answers 2

3

$(foo).on(bar, handler) is a jQuery event, which means AngularJS does not know the specifics of it, and will not (can not) run an apply-digest cycle after it to process all the bindings.

scope.$apply was made for this, and as you rightly say, fixes it. The rule of thumb is: if you implement UI functionality in an AngularJS application using other libraries (specifically: outside of apply-digest cycles), you must call scope.$apply yourself.

HTH!

Sign up to request clarification or add additional context in comments.

Comments

0

After element.append(el), try to compile again as you have just modified the DOM.

You could try something such as $compile(element)(scope); or $compile(element.contents())(scope);.

As said before me, I would also change the event handler as follows :

$('#button1').on('click', function(){
     scope.$apply( function(){
          //blablalba
     });
});

Also, justa piece of advice in case you would want to minify your code, I would declare the compile dependency using the following syntax :

.directive('directiveName',['$service1',''$service2,...,'$compile', function($service1, $service2,...,$compile){
     //blablabla
}]}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.