I'm using an ng-repeat
inside a directive's template.
myApp.directive("test", function () {
return {
restrict: 'C',
scope: {
bindVar: '='
},
template: '<div>\
<div class="item" ng-repeat="sel in bindVar">{{sel.display}}</div>\
</div>',
link: function ($scope, element, attrs) {
// setTimeout(function() {
alert($('.item').length); // <--- RETURNS 0, IF I ADD TIMEOUT RETURNS 3
// },0);
} // of link
} // of return
});
/
However, when the link()
function is called I don't seem to get access to the items that have been created. In order to do this I need to set a timeout of 0 (after that it works).
I read this in the following article: .html
I also saw a similar Stack Overflow answer where the OP marked Timeout as the answer: DOM elements not ready in AngularJS Directive's link() function
But c'mon, there's got to be another way!
I'm crossing my fingers that this hacky solution is wrong, and there's some way that angular provides a callback when the DOM has been created via a directive. Or do I really rely on .. timeouts? (really? :/)
I'm using an ng-repeat
inside a directive's template.
myApp.directive("test", function () {
return {
restrict: 'C',
scope: {
bindVar: '='
},
template: '<div>\
<div class="item" ng-repeat="sel in bindVar">{{sel.display}}</div>\
</div>',
link: function ($scope, element, attrs) {
// setTimeout(function() {
alert($('.item').length); // <--- RETURNS 0, IF I ADD TIMEOUT RETURNS 3
// },0);
} // of link
} // of return
});
http://jsfiddle/foreyez/t4590zbr/
However, when the link()
function is called I don't seem to get access to the items that have been created. In order to do this I need to set a timeout of 0 (after that it works).
I read this in the following article: http://lorenzmerdian.blogspot./2013/03/how-to-handle-dom-updates-in-angularjs.html
I also saw a similar Stack Overflow answer where the OP marked Timeout as the answer: DOM elements not ready in AngularJS Directive's link() function
But c'mon, there's got to be another way!
I'm crossing my fingers that this hacky solution is wrong, and there's some way that angular provides a callback when the DOM has been created via a directive. Or do I really rely on .. timeouts? (really? :/)
templateUrl
, which loads asynchronously. You are creating a race condition with the timeout - so that is not a solution
– New Dev
Commented
Feb 10, 2015 at 2:26
$timeout
is, in fact, a legitimate way to solve this when you use inline template
(as opposed to templateUrl
). It would not create a race condition.
What happens is, Angular goes over the DOM and collects directives and their pre- and post-link functions (by piling the directives). Then, the link functions for each directive for each node (i.e. DOM element) are executed.
Normally, the template for the node (to which the directive applies) is already part of the DOM. And so, if you have the following directive:
.directive("foo", function(){
return {
template: '<span class="fooClass">foo</span>',
link: function(scope, element){
// prints "<span class="fooClass">foo</span>"
console.log(element.html());
}
}
}
it can find the $(".fooClass")
element.
However, if a directive uses transclude: 'element'
, like ng-if
(ngIf.js) and ng-repeat
(ngRepeat.js) directives do, Angular rewrites the directive as a ment (pile.js), and so $(".item")
(in your example) is not there until ng-repeat
places it there. They do so in their scope.$watch
function (ngIf.js), depending on the value they are watching, and this may happen at the next digest cycle. So, even when your post-link function runs, the actual element that you are search for is still not there.
.directive("foo", function(){
return {
template: '<span ng-if="true" class="fooClass">foo</span>',
link: function(scope, element){
// prints "<!-- ngIf: true -->"
console.log(element.html());
}
}
}
But it will be there - definitely - when $timeout
runs.
The way I do this is with another directive. As an example:
.directive('elementReady', function() {
return {
restrict: 'A',
link: function(scope, elem, attr) {
//In here, you can do things like:
if(scope.$last) {
//this element is the last element in an ng-repeat
}
if(scope.$first) {
//first element in ng-repeat
}
//do jQuery and javascript calculations (elem has been added to the DOM at this point)
}
};
});
<table class="box-table" width="100%">
<thead>
<tr>
<th class='test' scope="col" ng-repeat="column in listcolumns" element-ready>{{column.title}}</th>
</tr>
</thead>
</table>
Obviously, you will need to customize how you propagate those events to your outer scope ($emit, through bound functions, etc).
Taking Joe's Answer and another answer I found on stackoverflow I was able to do this by:
myApp.directive('myRepeatDirective', function() {
return function(scope, element, attrs) {
if (scope.$last){
scope.$emit('LastElem');
}
};
});
and then in my original link function:
$scope.$on('LastElem', function(event){
alert($('.item').length);
});
and template looks like:
<div>
<div class="item" ng-repeat="sel in bindVar" my-repeat-directive>{{sel.display}}</div>
</div>
http://jsfiddle/foreyez/t4590zbr/3/
but I'm still not loving this solution.. seems kind of blehhh