Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: Custom $anchorScroll provider execution

I need to scroll to a specific anchor tag on page reload. I tried using $anchorScroll but it evaluates $location.hash(), which is not what I needed.

I wrote a custom provider based on the source code of $anchorScrollProvider. In it, it adds a value to the rootScope's $watch list, and calls an $evalAsync on change.

Provider:

zlc.provider('scroll', function() {
  this.$get = ['$window', '$rootScope', function($window, $rootScope) {
    var document = $window.document;
    var elm;

    function scroll() {
      elm = document.getElementById($rootScope.trendHistory.id);
      if (elm) elm.scrollIntoView();  
    }

    $rootScope.$watch(function scrollWatch() {return $rootScope.trendHistory.id;},
      function scrollWatchAction() {
        if ($rootScope.trendHistory.id) $rootScope.$eval(scroll);
      });

    return scroll;
  }];
});

Now, when I try to call the scroll provider in my controller, I must force a digest with $scope.$apply() before the call to scroll():

Controller:

//inside function called on reload
$scope.apply();
scroll();

Why must I call $scope.$apply()? Why isn't the scroll function evaluating in the Angular context when called inside the current scope? Thank you for your help!


1 Answers

I'm not sure what your thinking is behind using $rootScope.$eval(scroll) - since the scroll() function is already executing in a context where it has direct access to the $rootScope.

If I understand correctly, you want to be able to scroll to a particular element as denoted by an id which is stored in $rootScope.trendHistory.id.

When that id is changed, you want to scroll to that element (if it exists on the page).

Assuming this is a correct interpretation of what you are trying to achieve, here is how I might go about implementing it:

app.service('scrollService', function($rootScope) {
  $rootScope.trendHistory = {};

  $rootScope.$watch('trendHistory.id', function(val) {
    if (val) {
      elm = document.getElementById($rootScope.trendHistory.id);
      if (elm) elm.scrollIntoView();
    }
  });

  this.scrollTo = function(linkId) {
    $rootScope.trendHistory.id = linkId;
  }
});

This is a service (like your provider, but using the simpler "service" approach) which will set up a $watch on the $rootScope, looking for changes to $rootScope.trendHistory.id. When a change is detected, it scrolls to the element indicated if it exists - that bit is taken directly from your code.

So to use this in a controller, you'd inject the scrollService and then call its scrollTo() method with the ID as an argument. Example:

app.controller('AppController', function($scope, scrollService) {

        scrollService.scrollTo('some_id');

});

In your question, you mention this needing to occur on reload, so you'd just put the call into your reload handler. You could also just directly modify the value of $rootScope.trendHistory.id from anywhere in the app and it would also attempt to scroll.

Here is a demo illustrating the basic approach: http://plnkr.co/edit/cJpHoSemj2Z9muCQVKmj?p=preview

Hope that helps, and apologies if I misunderstood your requirements.

like image 56
Michael Bromley Avatar answered Sep 07 '26 21:09

Michael Bromley



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!