Angularjs: How to scroll the page after ng-show shows hidden elements?

10,189

I don't see any other solution than deferring the invocation of scrollTop() when using ng-show. You have to wait until the changes in your model are reflected in the DOM, so the elements become visible. The reason why they do not appear instantly is the scope life cycle. ng-show internally uses a watch listener that is only fired when the $digest() function of the scope is called after the execution of the complete code in your click handler.

See http://docs.angularjs.org/api/ng.$rootScope.Scope for a more detailed explanation of the scope life cycle.

Usually it should not be a problem to use a timeout that executes after this event with no delay like this:

setTimeout(function() {
    $(window).scrollTop(50);  
}, 0);

Alternative solution without timeout:

However, if you want to avoid the timeout event (the execution of which may be preceded by other events in the event queue) and make sure that scrolling happens within the click event handler. You can do the following in your controller:

$scope.$watch('itemsVisible', function(newValue, oldValue) {
    if (newValue === true && oldValue === false) {
        $scope.$evalAsync(function() {
            $(window).scrollTop(50);
        });
    }
});

The watch listener fires within the same invocation of $digest() as the watch listener registered by the ng-show directive. The function passed to $evalAsync() is executed by angular right after all watch listeners are processed, so the elements have been already made visible by the ng-show directive.

Share:
10,189
macene
Author by

macene

Updated on July 01, 2022

Comments

  • macene
    macene almost 2 years

    I have a list of hidden items. I need to show the list and then scroll to one of them with a single click. I reproduced the code here: http://plnkr.co/edit/kp5dJZFYU3tZS6DiQUKz?p=preview

    As I see in the console, scrollTop() is called before the items are visible, so I think that ng-show is not instant and this approach is wrong. It works deferring scrollTop() with a timeout, but I don't want to do that.

    Are there other solutions?

  • macene
    macene over 10 years
    I thought there was a more elegant way to do that, but I guess it's ok to use the timeout if that doesn't have problems depending on the browser. Thanks!
  • lex82
    lex82 almost 8 years
    Maybe the behaviour of $evalAsync has changed in newer angular versions.
  • Yulian
    Yulian almost 8 years
    The problem is that setTimeout doesn't work for all devices, because an mobile phone, for example, has less memory, thus executes the DOM operations slowly. So, it works on a desktop, but on a mobile - it doesn't. If I increase the timeout, it works on both devices, but on the desktop looks awful, because it scrolls down the page (because of showing and hiding different elements) and then goes up to the proper position. Any ideas how to tackle this?
  • lex82
    lex82 almost 8 years
    You can use the short timeout and in the timeout handler check whether the elements are visible (access the DOM directly). If not, you set a new timeout with a longer delay. However, if you start to access the DOM directly, you can as well do it right away and not use angular in the first place to toggle visibility of the affected elements.
  • Nick B
    Nick B almost 8 years
    better to inject the $timeout service and use this.$timeout(function() { ... so you're able to unit test it properly