I'm using ng-if to show and hide an element. When the element shows up, I want to call a service, that scrolls within the new element to a certain child (by Id). The problem is, that if I try to call my service function right after setting the element to visible, the DOM doesn't seem to be ready yet.
var myApp = angular.module('myApp',[]);
myApp.factory("ScrollService", function () {
return {
scroll: function (id) {
console.log(document.getElementById(id));
}
};
});
function MyCtrl($scope, ScrollService) {
$scope.visible = false;
$scope.toggleVisibility = function () {
$scope.visible = !$scope.visible;
if ($scope.visible) {
ScrollService.scroll("myId"); //output: null
}
};
}
document.getElementById()
will always result in null
.
Here is also a fiddle, that demonstrates the problem: http://jsfiddle.net/Dpuq2/
So is there any way, to trigger a function as soon as the DOM is ready after being manipulated by ng-if?
EDIT
Using the fiddle of MinkoGechev, I was able to reproduce my error in a more realistic environment and with using a directive instead of a service: FIDDLE
The problem seems to occur, because I'm using ng-repeat
inside of the ng-if
-container:
<div ng-controller="MyCtrl">
<div ng-if="visible">
<div id="myId" data-scroll="itemId">
<div id="xy"></div>
<div ng-repeat="item in items" id="{{ item.number }}">{{ item.number }}</div>
</div>
</div>
<button ng-click="toggleVisibility()">toggle</button>
</div>
Here is the according directive plus controller:
var myApp = angular.module('myApp',[]);
myApp.directive("scroll", function () {
return {
scope: {
scroll: '='
},
link: function (scope) {
scope.$watch('scroll', function (v) {
console.log(v, document.getElementById(scope.scroll));
});
},
transclude: true,
template: "<div ng-transclude></div>"
};
});
function MyCtrl($scope) {
$scope.visible = false;
$scope.itemId = "";
$scope.items = [];
for (var i = 1; i < 10; i++) {
$scope.items.push({
number: i,
text: "content " + i
});
}
$scope.toggleVisibility = function () {
$scope.visible = !$scope.visible;
if ($scope.visible) {
$scope.itemId = "3";
}
};
}
So as soon as I toggle the visibility of my container, I'm setting the Id of the element, to which I want to scroll:
$scope.itemId = "3"
If I'm using one of the numbers from 1 to 10 (the Ids of the elements created by ng-repeat) it will fail. If I'm using "xy" (the Id of one element that lives next to the ng-repeat elements) it succeeds.
For fast lookup: // register controller in html <div data-ng-controller="myCtrl" data-ng-init="init()"></div> // in controller $scope. init = function () { // check if there is query in url // and fire search in case its value is not empty }; This way, You don't have to wait till document is ready.
You can consider using transclusion inside a custom directive, to achieve the behavior you are looking for without using ng-repeat.
This directive executes at priority level 600.
Basic Difference between ng-if, ng-show and ng-hideng-if can only render data whenever the condition is true. It doesn't have any rendered data until the condition is true. ng-show can show and hide the rendered data, that is, it always kept the rendered data and show or hide on the basis of that directives.
Here is how you can achieve the effect you're looking for with directives:
var myApp = angular.module('myApp',[]);
myApp.directive("scroll", function () {
return {
scope: {
scroll: '='
},
link: function (scope) {
scope.$watch('scroll', function (v) {
//The value is true, so the element is visible
console.log(v, document.getElementById('myId'));
});
}
};
});
function MyCtrl($scope) {
$scope.visible = false;
$scope.toggleVisibility = function () {
$scope.visible = !$scope.visible;
};
}
Here is DEMO (open your console to see the logs).
NOTE: AngularJS force separation of concerns which leads to far more readable and maintainable code. One of the rules which you should follow while using "the Angular way" is to put all DOM manipulations ONLY inside directives.
Have you found a solution to your problem?
Since you mention that the problem seems to be related to ng-repeat, have you tried "scope.$last?"
I am by no means an experienced web developer, but I have had a similar issue where a tooltip won't show on items "generated" by an ng-repeat and got it working with a directive that applied the tooltip using "scope.$last"
As an example:
AppName.directive('directiveName', function () {
return function (scope, element, attrs) {
if (scope.$last) {
<-- Your code here -->
}
};
});
Maybe someone with more experience could give some more input.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With