AngularJS ngIf prevents finding element inside directive
angularjs, javascript
Solution
The `ngIf` directive works by using Angular's transclusion feature. What happens during the compile/link cycle is:
- The content inside the `ngIf` is removed from the DOM when it is compiled
- Angular runs the link functions. The `ngIf`'s link function is run before the link function of the directive using it. When `ngIf`'s link function runs, it uses `$scope.$watch()` to watch the value of the `ng-if` attribute.
- Your directive's link function runs, at this point the content of the `ngIf` is not part of the DOM
- The watch set up in step (2) is called, and `ngIf` will then call the `$transclude` function to insert the contents of the `ngIf` into the DOM if the `ng-if` attribute value is truthy.
- Any watch functions, `$timeout` calls or use of `$scope.$evalAsync` that you registered in your directive's link function will run.
So if you want to access elements inside the `ngIf`'s content, the code needs to run after step 4 above. This means that any functions registered with `$scope.$watch`, `$timeout` or `$scope.$evalAsync` in your directive's link function will work. For a one-time piece of setup code, I would probably opt for `$scope.$evalAsync`:
angular.directive('yourDirective', function () {
return {
...
link: function(scope, elem) {
scope.$evalAsync(function () {
// code that runs after conditional content
// with ng-if has been added to DOM, if the ng-if
// is enabled
});
}
};
});
Problem
I have an AngularJS directive that includes an `ngIf` and I would like to modify some of the DOM inside the `ngIf` in the directive link function. Unfortunately it seems that `ngIf` prevents me from finding DOM elements within it in the link function. Here is the code for the directive: ``` directive('column', function () { return { templateUrl: 'views/column.html', restrict: 'E', scope: { column: '=' }, controller: ['$scope', function ($scope) { $scope.editing = true; $scope.toggleEditing = function () { $scope.editing = !$scope.editing; }; }], link: function postLink(scope, element) { var select = element.find('select'); console.log(select); // See if it can find the select element // var types = scope.column.types(); // add types as options to the select element } }; }); ``` And here is the simplified html of the directive: ``` <div class="column"> <div>{{ column.title }}</div> <form name="columnForm" role="form" ng-if="editing"> <select></select> </form> </div> ``` Here is the link to the jsFiddle example http://jsfiddle.net/dedalusj/Y49Xx/1/ The `element.find` call in the link function returns an empty array but as soon as I remove the `ngIf` from the form it returns the proper select DOM element. I have the feeling that I'm doing this the wrong way. UPDATE Thanks for the answers but I found another solution. I simply created another directive that encapsulate the form, added it to the `column` directive template with `ng-if="editing"`. The form directive doesn't have it's own scope so it effectively operates out of the `column` directive scope and has always access to the `select` element because it's inside its DOM tree. I pay the cost of an extra directive but I don't have to use the `$timeout` hack. I created a new jsFiddle to illustrate the solution http://jsfiddle.net/dedalusj/nx3vX/1/ Thanks @Michael but I can't simply use the `ng-option` because the `types` array comes from an XML file and its elements are other angular.element objects which cannot be inserted easily with `ng-option`.