最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin4浏览0评论

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: /

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.

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/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.

Share Improve this question edited Nov 3, 2013 at 22:13 basilikum asked Nov 3, 2013 at 21:17 basilikumbasilikum 10.5k5 gold badges49 silver badges58 bronze badges 4
  • 3 Please, don't implement DOM into controller, use directive instead – Maxim Shoustin Commented Nov 3, 2013 at 21:22
  • @MaximShoustin I didn't implement it into a controller, but into a service. I also tried using a directive, with the same result. – basilikum Commented Nov 3, 2013 at 21:30
  • @basilikum In the link() function of a directive, you're sure the DOM of the element linked to this directive is ready. That's here that you must scroll the element to his child. – Blackhole Commented Nov 3, 2013 at 21:34
  • @Blackhole also a good suggestion, but it doesn't seem to be pletely ready, since elements inside a ng-repeat, are not yet present (see updated question). – basilikum Commented Nov 3, 2013 at 22:26
Add a ment  | 

2 Answers 2

Reset to default 2

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.

发布评论

评论列表(0)

  1. 暂无评论