Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 318
basilikum Avatar asked Nov 03 '13 21:11

basilikum


People also ask

How can we call document ready function in AngularJS?

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.

What can I use instead of NG-repeat?

You can consider using transclusion inside a custom directive, to achieve the behavior you are looking for without using ng-repeat.

At which priority level does the NG-if directive execute?

This directive executes at priority level 600.

What is the difference between ng show ng hide and Ng-if directive?

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.


2 Answers

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.

like image 199
Minko Gechev Avatar answered Nov 08 '22 09:11

Minko Gechev


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.

like image 1
WJK Avatar answered Nov 08 '22 09:11

WJK