Reputation: 1021
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
.
Upvotes: 23
Views: 11261
Reputation: 13
I was facing this same issue and i was able to resolve it using ng-show, this prevents this issue because ngIf removes the element it's applied to the DOM, so you won't be able to find it when it's not there.
so in your case:
<div class="column">
<div>{{ column.title }}</div>
<form name="columnForm" role="form" ng-show="editing">
<select></select>
</form>
will work OK.
Cheers.
Upvotes: 1
Reputation: 2927
The ngIf
directive works by using Angular's transclusion feature. What happens during the compile/link cycle is:
ngIf
is removed from the DOM when it is compiledngIf
'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.ngIf
is not part of the DOMngIf
will then call the $transclude
function to insert the contents of the ngIf
into the DOM if the ng-if
attribute value is truthy.$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
});
}
};
});
Upvotes: 12
Reputation: 23
You can put your code from the link function inside $timeout.
$timeout(function(){
var select = element.find('select');
console.log(select);
});
Don't forget to inject $timeout in your directive
directive('column', function ($timeout) {
Upvotes: 2
Reputation: 14104
As @moderndegree has said, ngIf
removes the element it's applied to from the DOM, so you won't be able to find it when it's not there. But, you could write your directive in a way to workaround that:
controller: function ($scope, $element, $timeout) {
$scope.toggleEditing = function () {
$scope.editing = !$scope.editing;
$timeout(function() {
var select = $element.find('select');
select.append('<option>Value 1</option>')
.append('<option>Value 2</option>')
.append('<option>Value 3</option>');
});
};
}
Updated jsFiddle here.
The trick here is to delay the find()
call by using $timeout
with a 0 interval in order to wait for Angular to update the DOM.
UPDATE
After giving some more thought to your code, I realize that perhaps you can let Angular do the hard work for you:
Javascript
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;
};
}],
};
});
HTML
<div class="column">
<div>{{ column.title }}</div>
<form name="columnForm" role="form" ng-if="editing">
<select ng-model="type" ng-options="type for type in column.types"></select>
</form>
</div>
Now you don't need to worry about finding the select
element at the right time and populating it. Angular does all of that for you. :)
Upvotes: 2