简体   繁体   中英

angularJS: Wait for ng-if to finish, to make sure that the DOM is ready

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM