link
link

Reputation: 1676

How to perform template manipulation after manual $compile?

I need to render a list of categories in a webapp I'm developing using AngularJS.

Each category can have an array of subcategories.

In order to render them, I created a recursive category directive that works like this:

This is the code of the directive:

.directive('category', 
  function(WeeklyCalendar, categoryRowsFactory, $compile){
    return {
      replace: true,
      restrict: 'A',
      templateUrl: 'new-category-tpl',
      scope: {
        category: '=categoryData'
      },
      link: function (scope, elem, attrs) {
        //recursive categories rendering
        if(scope.category.subcategories) {
          $compile('<div ng-repeat="sub in category.subcategories track by sub.id" category category-data="sub"></div>')
          (scope, function(cloned, scope){ elem.append(cloned) })
        }

        /* This works */
        //elem.append('aaa');

        /* This doesn't */
        elem.find('.content').append('aaa');
      }
    };
})

Everything working well so far, you can check the result here: codepen

So, each category has category-rows that are split in a header and a content, like this:

<div class="category">
    <div class="category-row">
        <div class="header"></div>
        <div class="content"></div>
    </div>
</div>

Now, I need to append some HTML to the div with class content from the link function. Anyway, using the classic element.find('.content').append('string') won't work, as find returns no result. (even if jQuery is included). Strangely enough, because if you inspect the HTML you can see that each category clearly has a category-row children, which contains a .content div. Simply appending to element works, but it's not enough for me.

By the way, this is the template for a category:

  <script type="text/ng-template" id="new-category-tpl">
    <div ng-class="{'category multicategory': category.subcategories, 'category single': category.type == 'Single', 'subcategory': category.type == 'Subcategory'}">
      <div ng-if="category.subcategories" class="supercategory">
        <strong>{{category.name}}</strong>
      </div>
      <div ng-if="category.type != 'WithSub'" class="category-row">
        <div class="header">{{category.name}}</div>
        <div class="content"></div>
      </div>
    </div>   
  </script>

Nothing fancy, as far as I can see.

I wasted an entire afternoon trying to understand what I'm missing, without success. Maybe you will be smarter/luckier. So, does anyone know why element.find(..) doesn't work in that link function?

Update

The solution from ExpertSystem works, up to a point. I am able to append in the $timeout call, that part is fine. But later I need to access the same element I append in the directive's controller, but the template is still uncompiled and it doesn't work.

Maybe I am using the wrong approach. This is what I am trying to achieve:

This becomes impossible if element becomes inaccessible, since I have to place events with absolute positioning and I need to know top and left. I Here is a short version of what I'm trying to achieve in the end:

.directive('category',function(){
   //...usual stuff
   controller: function($scope, $element){
       this.getPosition = function(date) {
           var cellSelector//compute selector for placeholder cell depending on date
           var cell = $element.find(cellSelector);
           return { top: cell.top(), left: cell.left()}
       }
   }
})

.directive('event',function(){
    //...usual stuff
    require: '^category',
    link: function(scope, elem, attrs, categoryCtrl) {
        var position = categoryCtrl.getPosition(scope.event.date);
        element.css(positiong);
    }
})

I am out of ideas. Can someone think of a better way to achieve what I described?

Upvotes: 0

Views: 113

Answers (1)

gkalpak
gkalpak

Reputation: 48211

The "problem" is that ngIf (among other directives) removes the template from the DOM and decides what/if to append at a later time (after a few $digest loops).

So at the time you try to access .content, the element's innerHTML looks like this:

<!-- ngIf: category.subcategories -->
<!-- ngIf: category.type!=='WithSub' -->

The "safest" to access .content once it is rendered is to wrap your code into a $timeout callback:

$timeout(function () {
    elem.find('.content').text('aaa');
});

See, also, this short demo.

Upvotes: 2

Related Questions