Search code examples
angularjsanchor-scroll

AngularJs with hash in href breaks scrolling and transforms url to hash+fwslash+id


How to configure AngularJs to scroll to anchor element specified in href with an #id?

The problem

The anchor link with a hash, ex. href="#recommanded" becomes url#/recommanded instead of url#recommanded so it will not scroll as expected. If angularjs library is not loaded, it works fine. In the statusbar when I move the cursor on top of the link the browser's statusbar correctly displays the link I'm about to be heading, but after clicking it will get transformed and will not scroll correctly. I'm not using angular's routes.

Snippets from my AngularJS code that might be interfering with scrolling / js location manipulations:

...
app.config([ '$httpProvider', '$compileProvider', function($httpProvider, $compileProvider) {
    $compileProvider.debugInfoEnabled(false);
    // Initialize get if not there
    if (!$httpProvider.defaults.headers.get) {
        $httpProvider.defaults.headers.get = {};
    }

    // Disable IE ajax request caching
    $httpProvider.defaults.headers.get['If-Modified-Since'] = '0';
} ]);
...
app.directive('jumpForm', function($timeout) {
    return {
        restrict : 'A',
        link : function(scope, elem) {

            // set up event handler on the form element
            elem.on('submit', function() {
                $timeout(function() {
                    // find the first invalid element
                    var firstInvalid = elem[0].querySelector('.has-error');

                    // if we find one, set focus
                    if (firstInvalid) {

                        $('html, body').animate({
                            duration : 2000,
                            scrollTop : (firstInvalid.offsetTop + 100)
                        });
                    }
                }, 200);

            });
        }
    };
});
...
app.factory('httpResponseInterceptor', [ '$q', '$location', '$window', function($q, $location, $window) {
    return {
        response : function(response) {
            if (response && response.data && response.data.errorCode == 'AUTHENTICATION_REQUIRED') {
                var _context = $("body").attr("data-context");
                var redirectUrl = _context + '/registration?msg=session_expired';
                $window.location.href = redirectUrl;
                return $q.reject(response);
            }
            return response;
        }
    }
} ]);

app.config([ '$httpProvider', function($httpProvider) {
    $httpProvider.interceptors.push('httpResponseInterceptor');
} ]);

A call with hashtag:

<a href="#recommanded"> </a>

Trials and errors

Internal workarounds

anchorScroll

In the thread How to handle anchor hash linking in AngularJS it is advised to insert this snippet:

app.controller('TestCtrl', function($scope, $location, $anchorScroll) {
    $scope.scrollTo = function(id) {
        $location.hash(id);
        $anchorScroll();
    }
});

And in calling it:

<a ng-click="scrollTo('recommanded')">Foo</a>

This results in no error on the console but just the same behavior as before. Url is transformed no scrolling to anchor. Also I don't like the idea to end up having an ng-click function for this.

Appended my angularjs with:

app.run(function($rootScope, $location, $anchorScroll) {
    //when the route is changed scroll to the proper element.
    $rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) 
    {
        if($location.hash()) $anchorScroll();  
    });
});

and called it as

<a href="#/test#recommanded">Test/Foo</a>

Resulted in no error but scroll not working neither.

Some are hinting to just do a #/#recommanded in href. Use target="_self". None of them worked out for me.

External workaround solutions

Directive scrollTo

   app.directive('scrollto',
   function ($anchorScroll,$location) {
        return {
            link: function (scope, element, attrs) {
                element.click(function (e) {
                    e.preventDefault();
                    $location.hash(attrs["scrollto"]);
                    $anchorScroll();
                });
            }
        };
})

The html is looking like:

<a href="" scrollTo="recommanded">link</a>

This one actually works, but would prefer a solution where the call href="#recommended" just works. Or at least animate the scroll.

angular-scroll's du-smooth-scroll

Added duScroll to my angularApp.js: var app = angular.module('app', [ ..., 'duScroll' ]); I have the angular-scroll.min.js file loaded on the page with angularApp.js without error. I call <a du-smooth-scroll href="#recommended-to-you">recommanded</a> for an existing div with that id. But it wont do anything, no js errors but will not move. Tried duScroll module with adding explicit ease duration etc. settings, no errors but not working.


Solution

  • html5Mode

    As described in angular add slash before hash in url I tried to turn on the html5 mode by adding:

    app.config(function($locationProvider) {
              $locationProvider.html5Mode(true);
            })
    

    I have set up the base tag in the head section.

    These settings made it to start working as expected.

    ... but I would mention that once the location in the address bar got updated to the referenced hash if I clicked the link again, it just went down a bit and not to the referenced location. Clicking on links with different anchors it works well. Not sure if this is a general bug or only some of my settings are backfiring.