I have one auto-carousel
directive which iterates through the linked element's children.
The children however are not yet loaded in the DOM, because their ng-if
s expressions have not been parsed yet.
How can I make sure the parent directive knows there have been changes to it's DOM tree?
<ul class="unstyled" auto-carousel>
<li class="slide" ng-if="name">{{name}}</li>
...
<li class="slide" ng-if="email">{{email}}</li>
</ul>
I could use $timeout
but that feels unreliable. I could also use ng-show
instead of ng-if
but that does not answer the question and not what I need.
So here's what I ended up doing:
I discovered you could pass a function to $scope.$watch
. From there, it's pretty straightforward to return the value of the expression you want to watch for changes. It will work exactly like passing a key string for a property on the scope.
link: function ($scope, $el, $attrs) {
$scope.$watch(
function () { return $el[0].childNodes.length; },
function (newValue, oldValue) {
if (newValue !== oldValue) {
// code goes here
}
}
);
}
I am watching childNodes
, not children
, because the childNodes
list holds elements as well as text nodes and comments. This is priceless because Angular uses comment placeholders for directives like ng-repeat
, ng-if
, ng-switch
and ng-include
which perform transclusion and alter the DOM, while children
only holds elements.
If you need to watch for any changes deeper in the element's dom, MutationObserver is the way to go :
.directive('myDirective', function() {
return {
...
link: function(scope, element, attrs) {
var observer = new MutationObserver(function(mutations) {
// your code here ...
});
observer.observe(element[0], {
childList: true,
subtree: true
});
}
};
});
I created a directive module for this angular-dom-events
In your case you could
<ul class="unstyled" auto-carousel>
<li class="slide" ng-if="name" dom-on-create="nameCreated()">{{name}}</li>
<li class="slide" ng-if="email" dom-on-destroy="emailDestroyed()">{{email}}</li>
</ul>
Currently only supports dom-on-create
and dom-on-destroy
, but has better performance then the accepted answer because it will only fire once for each dom event, rather than repeatedly check the $watch callback.
Although I don't think it is with angular's recommendations, you could use ng-init which fires upon the initialization of the element:
<ul class="unstyled" auto-carousel>
<li class="slide" ng-if="name" ng-init="recheck()">{{name}}</li>
<li class="slide" ng-if="email" ng-init="recheck()">{{email}}</li>
</ul>
You could try to compile the directive contents first inside your link function. For example:
angular.module('myApp').directive('autoCarousel', ['$compile', function ($compile) {
return {
templateUrl: 'views/auto-carousel.html',
restrict: 'A',
replace: true,
link: function (scope, element, attr) {
$compile(element.contents())(scope);
// your code goes here
}
}
}]);