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

2

8

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?

Bridgettebridgewater answered 10/11, 2013 at 11:8 Comment(1)
This seems to be a special case of this related question: https://mcmap.net/q/88837/-angularjs-how-to-run-additional-code-after-angularjs-has-rendered-a-template/302793Sulfanilamide
S
16

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.

Sulfanilamide answered 10/11, 2013 at 12:11 Comment(5)
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!Bridgettebridgewater
Maybe the behaviour of $evalAsync has changed in newer angular versions.Sulfanilamide
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?Paramagnetic
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.Sulfanilamide
better to inject the $timeout service and use this.$timeout(function() { ... so you're able to unit test it properlyScarletscarlett
B
0

You can use the $anchorScroll.
Here is the documentation:

https://docs.angularjs.org/api/ng/service/$anchorScroll

Example:

$scope.showDiv = function()
{
   $scope.showDivWithObjects = true;
   $location.hash('div-id-here');
   $anchorScroll();
}
Bogoch answered 23/3, 2017 at 17:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.