322

Do any of you know how to nicely handle anchor hash linking in AngularJS?

I have the following markup for a simple FAQ-page

<a href="#faq-1">Question 1</a>
<a href="#faq-2">Question 2</a>
<a href="#faq-3">Question 3</a>

<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="fa1-3">Question 3</h3>

When clicking on any of the above links AngularJS intercepts and routes me to a completely different page (in my case, a 404-page as there are no routes matching the links.)

My first thought was to create a route matching "/faq/:chapter" and in the corresponding controller check $routeParams.chapter after a matching element and then use jQuery to scroll down to it.

But then AngularJS shits on me again and just scrolls to the top of the page anyway.

So, anyone here done anything similar in the past and knows a good solution to it?

Edit: Switching to html5Mode should solve my problems but we kinda have to support IE8+ anyway so I fear it's not an accepted solution :/

Sunil Garg
  • 14,608
  • 25
  • 132
  • 189
Rasmus
  • 4,190
  • 7
  • 22
  • 32
  • I think angular suggests to use `ng-href=""` instead. – piggyback Feb 05 '13 at 16:52
  • 10
    I think ng-href is only applicable if the url contains dynamic data that needs to be bound to an ng-model. I kind of wonder if you assign a hashPrefix to the locationProvider if it will ignore the link's to ID's: http://docs.angularjs.org/guide/dev_guide.services.$location – Adam Feb 05 '13 at 16:57
  • Adam is correct on the ng-href usage. – Rasmus Feb 05 '13 at 18:39
  • Possible duplicate of [Anchor links in Angularjs?](http://stackoverflow.com/questions/14026537/anchor-links-in-angularjs) – Michaël Benjamin Saerens Nov 22 '16 at 14:51
  • This is also an issue for new Angular: https://stackoverflow.com/questions/36101756/angular2-routing-with-hashtag-to-page-anchor – phil294 Aug 10 '17 at 08:05
  • ng-href does not work for this usage. You will get the same result as a regular href attr. – oaklandrichie Mar 28 '18 at 17:51
  • @Rasmus - found this thread trying to solve an Ionic `overflow-scroll` interfering with $ionScrollDelegate - "...but then AngularJS shits on me again" Made me laugh. – rolinger May 05 '21 at 16:27
  • @rolinger Thanks for bringing this old crap to my attention again :D I still remember how frustrated I was with AngularJS at the time! – Rasmus Jun 01 '21 at 10:35

28 Answers28

378

You're looking for $anchorScroll().

Here's the (crappy) documentation.

And here's the source.

Basically you just inject it and call it in your controller, and it will scroll you to any element with the id found in $location.hash()

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

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

<div id="foo">Here you are</div>

Here is a plunker to demonstrate

EDIT: to use this with routing

Set up your angular routing as usual, then just add the following code.

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

and your link would look like this:

<a href="#/test?scrollTo=foo">Test/Foo</a>

Here is a Plunker demonstrating scrolling with routing and $anchorScroll

And even simpler:

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 your link would look like this:

<a href="#/test#foo">Test/Foo</a>
georgeawg
  • 48,608
  • 13
  • 72
  • 95
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • 5
    Problem can come when you add routing: if add ngView each change of url's hash would trigger route reload... In your example there is no routing and url does not reflect current item... But thanks for pointing to $anchorScroll – Valentyn Shybanov Feb 05 '13 at 23:03
  • I'm very new to AngularJS, just started tinkering last week coming from a jQuery background, so this may not the the "Angular" way to do this. However, it bothers me that the routing solution loses my controller's current state when (technically) only the routeParams changed. I fixed this by moving my data into a service I called "myData" that shares the data between the controllers and route loads. http://plnkr.co/edit/5AqcjbWCOQ6fhaiTH0T3 – Walter Stabosz Mar 28 '13 at 18:37
  • Come to think of it, I suppose it's better to just store the static data in the MainCtrl (hey, like I said, I'm new) – Walter Stabosz Mar 28 '13 at 19:02
  • @WalterStabosz: You might want to look into $rootScope as well. It's a really good solution for persisting data between controllers. – Ben Lesh Mar 28 '13 at 20:11
  • 1
    @blesh, calling location.hash(X) changes the page since routing controls the views. – dsldsl May 10 '13 at 23:39
  • 25
    This solution causes my whole application to re-render. –  Jun 03 '13 at 14:01
  • 3
    @dsldsl && @OliverJosephAsh: $location.hash() will not reload the page. If it is, there's something else going on. [Here is the same plunk with the time being written out as the page is loading, you'll see it doesn't change](http://plnkr.co/edit/MuOY3mE1SfWAHkD1ijpk?p=preview) If you want to scroll to an anchor tag on the current page without reloading the route, you'd just do a regular link `foo`. My code sample was to show scrolling to an id on routechange. – Ben Lesh Jun 04 '13 at 14:49
  • 4
    @blesh: I'd recommend you also remove the hash after scrolling to the desired section, this way the URL is not polluted with stuff which really should not be there. Use this: `$location.search('scrollTo', null)` – kumarharsh Sep 14 '13 at 17:08
  • Is there a way to scroll using anchorscholl while compensating for a header that sticks to the top of the page? (Basically, anchorscroll with an offset) – Richard Jelte Nov 05 '13 at 19:18
  • @RichardJelte not directly, you'd need to add a hidden element, like an anchor or something that was next to your content with an offset, then scroll to that with $anchorScroll. – Ben Lesh Nov 06 '13 at 04:02
  • @blesh: How do i set the offset of the hidden element when the site is completely responsive? – Mohamed Hussain Jan 09 '14 at 14:09
  • @MohamedHussain you might want to post a question on this site, as I'm not completely sure what you're asking. – Ben Lesh Jan 09 '14 at 16:01
  • 1
    @MohamedHussain it looks like anchorScroll now supports a yoffset: https://docs.angularjs.org/api/ng/service/$anchorScroll#yOffset – Michael Jan 28 '15 at 22:01
  • As added in my comment to the solution by @lincolnge below, the hash-in-hash works great for me so long as I explicitly call $anchorScroll after a timeout. Seems like 1 ms is too small but 100 works nicely. Just dropping my tuppence – thynctank Jul 06 '15 at 20:49
  • And if you are using hash routing (because you have to maintain something for IE8, for instance) setting hash explicitly definitely will retrigger location change events, and you get into temporal paradox hell where your doppelgänger screws everything up unless you litter your code with state-change flags, etc. But that's kinda normal in ng-world. – thynctank Jul 06 '15 at 20:52
  • I found that simply passing the id to anchorScroll avoided an unfortunate page reload. – Kzqai Jan 30 '17 at 19:25
  • How would you make this work with Angular v 1.5.7? Currently, this way does not work on this latest stable version of Angular. – Kyle Vassella Mar 13 '17 at 23:59
  • @BenLesh what is wrong here https://stackoverflow.com/q/45723741/1481690 after i prettify url it is not working second time – Peru Aug 17 '17 at 17:38
  • Hi sorry for reviving a dead thread. Is there anyway we can use anchorscroll from external links like Outlook email's link feature where if they click www.site.com/farm then it would scroll down to www.site.com/index#farm ? (since we can't use html or js or codes in outlook which means external links usage only) Thanks! – Nolan Kr May 05 '20 at 02:22
171

In my case, I noticed that the routing logic was kicking in if I modified the $location.hash(). The following trick worked..

$scope.scrollTo = function(id) {
    var old = $location.hash();
    $location.hash(id);
    $anchorScroll();
    //reset to old to keep any additional routing logic from kicking in
    $location.hash(old);
};
Drewness
  • 5,004
  • 4
  • 32
  • 50
slugslog
  • 1,748
  • 1
  • 10
  • 10
  • 5
    Brilliant thanks for that, the routing logic absolutely refused to behave even when using @blesh's .run(...) solution, and this sorts it. – ljs Apr 25 '13 at 17:07
  • 8
    Your "saving the old hash" trick has been an absolute lifesaver. It prevents the page reloads while keeping the route clean. Awesome idea! – ThisLanham Dec 29 '13 at 21:33
  • 2
    Nice One. But after implementing your solution, the url is not updating the value of the target id. – Mohamed Hussain Jan 09 '14 at 14:10
  • 1
    I had the same experience as Mohamed... It did indeed stop the reload but it displays the hashless route (and $anchorScroll had no effect). 1.2.6 Hmmmm. – michael Jan 09 '14 at 21:05
  • 1
    Thank you slugslog! This was a great solution to getting around the route reload. – Jonathan Aug 18 '14 at 17:10
  • I, too am having the issue of the `old` not working anymore. I am using `1.2.21` What I did was `var hash = (condition) ? 'uploadForm' : ''; $location.hash(hash); $anchorScroll();` and that worked. I am toggling a div. When it is visible, I wanted it to scroll to the element. – Ronnie Sep 30 '14 at 18:12
  • I added answer because I don't think we need to set it back just use .replace – Jackie Feb 04 '15 at 19:52
  • Why not just `if($location.hash()) $anchorScroll()` with `href="#/path#target"`? – vp_arth Jun 11 '15 at 03:59
  • This works great. Now it would just be great if we found a way to add the #target to the url. Then it would be even better. – J-bob Jun 15 '15 at 07:37
  • 3
    I use `$location.hash(my_id); $anchorScroll; $location.hash(null)`. It prevents the reload and I don't have to manage the `old` variable. – MFB Jun 25 '15 at 05:11
  • Hey I gave this a go on angular 1.4x and it doesn't work :[ it stops the page from reloading but it doesn't scroll – Yeysides Nov 09 '15 at 19:14
  • Does all of this still apply to Angular v 1.6.0 or have things changed? I'm using ngRoute and trying to implement these methods and none of them work! It takes me to the proper route, but not to the correct hash location on the page :( http://stackoverflow.com/q/41494330/6647188 – Kyle Vassella Jan 07 '17 at 19:22
53

There is no need to change any routing or anything else just need to use target="_self" when creating the links

Example:

<a href="#faq-1" target="_self">Question 1</a>
<a href="#faq-2" target="_self">Question 2</a>
<a href="#faq-3" target="_self">Question 3</a>

And use the id attribute in your html elements like this:

<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="faq-3">Question 3</h3>

There is no need to use ## as pointed/mentioned in comments ;-)

Mauricio Gracia Gutierrez
  • 10,288
  • 6
  • 68
  • 99
41
<a href="##faq-1">Question 1</a>
<a href="##faq-2">Question 2</a>
<a href="##faq-3">Question 3</a>

<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="faq-3">Question 3</h3>
lincolnge
  • 1,037
  • 14
  • 19
  • Awesome! By far the simplest solution, but any idea how to link to a an anchor on a separate page? (i.e. /products#books ) – 8bithero May 01 '14 at 19:50
  • I think it is the same as the solution (/products##books) in AngularJS – lincolnge May 02 '14 at 05:03
  • 1
    From my experience, href="##" works only when $anchorScroll is injected. – Brian Park Jul 15 '14 at 19:15
  • This isn't working for me. It still replaces my URL, so instead of http://localhost/#/app/folder/250, when I click on the tab the URL changes to http://localhost/#tab-1.. so impossible to link to. If I try http://localhost/#/app/folder/250##tab-1 (as lincoln suggests) I get the default route instead. – geoidesic Mar 04 '15 at 01:29
  • 9
    this seems relatively simple but its not working :-( – Sandip Bantawa Apr 06 '15 at 09:39
  • 4
    I added target="_self" and it worked like charm for all type of navigation within page ( read sliders, going to different sections and so on). Thank you for sharing this great and simplest trick. – Kumar Nitesh Jun 13 '15 at 14:14
  • I can only speak to my experience today with this (we are locked into 1.2.x so we can't pass an ID to $anchorScroll, etc) but using the hash-in-hash works for me. Consistently. **Only there needs to be a timeout for it to work consistently.** – thynctank Jul 06 '15 at 20:47
  • Didn't work for me, I had to add target="_self" as per @Mauricio-Gracia-Gutierrez's answer below. Adding the ## was not necessary (didn't change anything) – GôTô Oct 18 '16 at 15:14
20

If you always know the route, you can simply append the anchor like this:

href="#/route#anchorID

where route is the current angular route and anchorID matches an <a id="anchorID"> somewhere on the page

cab1113
  • 209
  • 2
  • 3
  • 1
    This triggers a normal AngularJS route change and is therefore discouraged. In my case it was very visual since the YouTube videos in the FAQ/Help page reloaded. – Robin Andersson Dec 10 '13 at 13:19
  • 9
    @RobinWassén-Andersson by specifying `reloadOnSearch: false` for that route in your routes config, angular will not trigger a route change and will just scroll to the id. In combination with the full route specified in the `a` tag, I would say this is the simplest and most straightforward solution. – Elise Feb 11 '14 at 09:51
  • Thank you. This helped me. I don't use any custom routes in my app, so doing a href="#/#anchor-name" worked great! – Jordan Jan 13 '15 at 21:57
14

$anchorScroll works for this, but there's a much better way to use it in more recent versions of Angular.

Now, $anchorScroll accepts the hash as an optional argument, so you don't have to change $location.hash at all. (documentation)

This is the best solution because it doesn't affect the route at all. I couldn't get any of the other solutions to work because I'm using ngRoute and the route would reload as soon as I set $location.hash(id), before $anchorScroll could do its magic.

Here is how to use it... first, in the directive or controller:

$scope.scrollTo = function (id) {
  $anchorScroll(id);  
}

and then in the view:

<a href="" ng-click="scrollTo(id)">Text</a>

Also, if you need to account for a fixed navbar (or other UI), you can set the offset for $anchorScroll like this (in the main module's run function):

.run(function ($anchorScroll) {
   //this will make anchorScroll scroll to the div minus 50px
   $anchorScroll.yOffset = 50;
});
T J
  • 42,762
  • 13
  • 83
  • 138
Rebecca
  • 1,064
  • 13
  • 12
  • Thanks. How would you implement your strategy for a hash link combined with a route change? Example: click this nav item, which opens a different view and scrolls down to a specific `id` in that view. – Kyle Vassella Jan 06 '17 at 00:30
  • Sry to pester..If you get a chance could you give my Stack question a glance? I feel your answer here has gotten me so close but I just can't implement it: http://stackoverflow.com/questions/41494330/hash-linking-with-routing-anchorscroll-troubleshooting. I too am using `ngRoute` and the newer version of Angular. – Kyle Vassella Jan 08 '17 at 22:58
  • I'm sorry I haven't tried that particular case... but have you taken a look at [$location.search()](https://docs.angularjs.org/api/ng/service/$location) or [$routeParams](https://docs.angularjs.org/api/ngRoute/service/$routeParams)? Perhaps you could use one or the other on initialization of your controller - if your scrollTo search param is in the URL, then the controller could use $anchorScroll as above to scroll the page. – Rebecca Jan 10 '17 at 10:07
  • 2
    By passing the Id directly to $anchorScroll, my routes changed from something like /contact#contact to just /contact . This should be the accepted answer imho. – RandomUs1r Jun 19 '17 at 19:47
13

This was my solution using a directive which seems more Angular-y because we're dealing with the DOM:

Plnkr over here

github

CODE

angular.module('app', [])
.directive('scrollTo', function ($location, $anchorScroll) {
  return function(scope, element, attrs) {

    element.bind('click', function(event) {
        event.stopPropagation();
        var off = scope.$on('$locationChangeStart', function(ev) {
            off();
            ev.preventDefault();
        });
        var location = attrs.scrollTo;
        $location.hash(location);
        $anchorScroll();
    });

  };
});

HTML

<ul>
  <li><a href="" scroll-to="section1">Section 1</a></li>
  <li><a href="" scroll-to="section2">Section 2</a></li>
</ul>

<h1 id="section1">Hi, I'm section 1</h1>
<p>
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis. 
 Summus brains sit​​, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris. 
Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat cerebella viventium. 
Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv ingdead.
</p>

<h1 id="section2">I'm totally section 2</h1>
<p>
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis. 
 Summus brains sit​​, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris. 
Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat cerebella viventium. 
Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv ingdead.
</p>

I used the $anchorScroll service. To counteract the page-refresh that goes along with the hash changing I went ahead and cancelled the locationChangeStart event. This worked for me because I had a help page hooked up to an ng-switch and the refreshes would esentially break the app.

Michael
  • 2,973
  • 1
  • 27
  • 67
KhalilRavanna
  • 5,768
  • 3
  • 28
  • 24
  • I like your directive solution. However how would you do it you wanted to load another page and scroll to the anchor location at the same time. Without angularjs it would nomally be href="location#hash". But your directive prevents the page reload. – CMCDragonkai Aug 20 '13 at 13:11
  • @CMCDragonkai with my directive, I'm not sure because I make use of the call to $anchorScroll which it looks like only handles scrolling to an element currently on the page. You might have to mess with [$location](http://docs.angularjs.org/api/ng.$location) or [$window](http://docs.angularjs.org/api/ng.$window) to get something involving a change of page. – KhalilRavanna Aug 21 '13 at 14:34
  • 2
    You need to unsubscirbe from locationChangeStart event: var off = scope.$on('$locationChangeStart', function(ev) { off(); ev.preventDefault(); }); –  May 21 '14 at 13:04
  • Good catch @EugeneTskhovrebov, I went ahead and added that to the answer in an edit. – KhalilRavanna May 21 '14 at 17:41
5

Try to set a hash prefix for angular routes $locationProvider.hashPrefix('!')

Full example:

angular.module('app', [])
  .config(['$routeProvider', '$locationProvider', 
    function($routeProvider, $locationProvider){
      $routeProvider.when( ... );
      $locationProvider.hashPrefix('!');
    }
  ])
nakhli
  • 4,009
  • 5
  • 38
  • 61
Maxim Grach
  • 4,300
  • 2
  • 20
  • 19
  • This doesn't affect the outcome. It would be sweet though. – Rasmus Feb 05 '13 at 19:56
  • 2
    Are you sure. Why doesn't this work? If hash prefix is !, then hash routing should be #!page. Therefore AngularJS should detect when it is just a #hash, it should anchor scroll automatically and work for both HTML5 mode urls and hash mode urls. – CMCDragonkai Aug 20 '13 at 13:25
5

I got around this in the route logic for my app.

function config($routeProvider) {
  $routeProvider
    .when('/', {
      templateUrl: '/partials/search.html',
      controller: 'ctrlMain'
    })
    .otherwise({
      // Angular interferes with anchor links, so this function preserves the
      // requested hash while still invoking the default route.
      redirectTo: function() {
        // Strips the leading '#/' from the current hash value.
        var hash = '#' + window.location.hash.replace(/^#\//g, '');
        window.location.hash = hash;
        return '/' + hash;
      }
    });
}
nicksanta
  • 111
  • 3
  • 7
  • 1
    This doesn't work: Error: [$injector:modulerr] Failed to instantiate module angle due to: Error: invalid 'handler' in when() – geoidesic Mar 04 '15 at 01:34
5

This is an old post, but I spent a long time researching various solutions so I wanted to share one more simple one. Just adding target="_self" to the <a> tag fixed it for me. The link works and takes me to the proper location on the page.

However, Angular still injects some weirdness with the # in the URL so you may run into trouble using the back button for navigation and such after using this method.

Benjamin
  • 463
  • 1
  • 5
  • 13
4

This may be a new attribute for ngView, but I've been able to get it anchor hash links to work with angular-route using the ngView autoscroll attribute and 'double-hashes'.

ngView (see autoscroll)

(The following code was used with angular-strap)

<!-- use the autoscroll attribute to scroll to hash on $viewContentLoaded -->    
<div ng-view="" autoscroll></div>

<!-- A.href link for bs-scrollspy from angular-strap -->
<!-- A.ngHref for autoscroll on current route without a location change -->
<ul class="nav bs-sidenav">
  <li data-target="#main-html5"><a href="#main-html5" ng-href="##main-html5">HTML5</a></li>
  <li data-target="#main-angular"><a href="#main-angular" ng-href="##main-angular" >Angular</a></li>
  <li data-target="#main-karma"><a href="#main-karma" ng-href="##main-karma">Karma</a></li>
</ul>
michael
  • 4,377
  • 8
  • 47
  • 73
3

I could do this like so:

<li>
<a href="#/#about">About</a>
</li>
T J
  • 42,762
  • 13
  • 83
  • 138
Niru
  • 41
  • 1
2

Here is kind of dirty workaround by creating custom directive that will scrolls to specified element (with hardcoded "faq")

app.directive('h3', function($routeParams) {
  return {
    restrict: 'E',
    link: function(scope, element, attrs){        
        if ('faq'+$routeParams.v == attrs.id) {
          setTimeout(function() {
             window.scrollTo(0, element[0].offsetTop);
          },1);        
        }
    }
  };
});

http://plnkr.co/edit/Po37JFeP5IsNoz5ZycFs?p=preview

Valentyn Shybanov
  • 19,331
  • 7
  • 66
  • 59
  • This does indeed work, but it is, as you said, dirty. Very dirty. Let's see if someone else can come up with a prettier solution, or I'll have to go with this. – Rasmus Feb 05 '13 at 19:59
  • Angular already has scrollTo functionality built in via `$anchorScroll`, see my answer. – Ben Lesh Feb 05 '13 at 21:34
  • Changed plunker to be less dirty: it uses $location.path() so there is no hardcoded "faq" in source. And also tried to use $anchorScroll, but seems due to routing it does not work... – Valentyn Shybanov Feb 05 '13 at 23:39
2
<a href="/#/#faq-1">Question 1</a>
<a href="/#/#faq-2">Question 2</a>
<a href="/#/#faq-3">Question 3</a>
felipe_dmz
  • 89
  • 1
  • 10
2

If you don't like to use ng-click here's an alternate solution. It uses a filter to generate the correct url based on the current state. My example uses ui.router.

The benefit is that the user will see where the link goes on hover.

<a href="{{ 'my-element-id' | anchor }}">My element</a>

The filter:

.filter('anchor', ['$state', function($state) {
    return function(id) {
        return '/#' + $state.current.url + '#' + id;
    };
}])
Reimund
  • 2,346
  • 1
  • 21
  • 25
2

My solution with ng-route was this simple directive:

   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="yourid">link</a>
MrFlo
  • 335
  • 3
  • 8
  • Would you not have to specify the attribute like `scroll-to="yourid"` and name the directive `scrollTo` (and access the attribute as `attrs["scrollTo"]`? Besides, without explicite jQuery inclusion the handler has to be bound with `element.on('click', function (e) {..})`. – tzelleke Sep 25 '17 at 13:04
1

You could try to use anchorScroll.

Example

So the controller would be:

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

And the view:

<a href="" ng-click="scrollTo('foo')">Scroll to #foo</a>

...and no secret for the anchor id:

<div id="foo">
  This is #foo
</div>
Edmar Miyake
  • 12,047
  • 3
  • 37
  • 38
1

I was trying to make my Angular app scroll to an anchor opon loading and ran into the URL rewriting rules of $routeProvider.

After long experimentation I settled on this:

  1. register a document.onload event handler from the .run() section of the Angular app module.
  2. in the handler find out what the original has anchor tag was supposed to be by doing some string operations.
  3. override location.hash with the stripped down anchor tag (which causes $routeProvider to immediately overwrite it again with it's "#/" rule. But that is fine, because Angular is now in sync with what is going on in the URL 4) call $anchorScroll().

angular.module("bla",[]).}])
.run(function($location, $anchorScroll){
         $(document).ready(function() {
  if(location.hash && location.hash.length>=1)      {
   var path = location.hash;
   var potentialAnchor = path.substring(path.lastIndexOf("/")+1);
   if ($("#" + potentialAnchor).length > 0) {   // make sure this hashtag exists in the doc.                          
       location.hash = potentialAnchor;
       $anchorScroll();
   }
  }  
 });
1

I am not 100% sure if this works all the time, but in my application this gives me the expected behavior.

Lets say you are on ABOUT page and you have the following route:

yourApp.config(['$routeProvider', 
    function($routeProvider) {
        $routeProvider.
            when('/about', {
                templateUrl: 'about.html',
                controller: 'AboutCtrl'
            }).
            otherwise({
                redirectTo: '/'
            });
        }
]);

Now, in you HTML

<ul>
    <li><a href="#/about#tab1">First Part</a></li>
    <li><a href="#/about#tab2">Second Part</a></li>
    <li><a href="#/about#tab3">Third Part</a></li>                      
</ul>

<div id="tab1">1</div>
<div id="tab2">2</div>
<div id="tab3">3</div>

In conclusion

Including the page name before the anchor did the trick for me. Let me know about your thoughts.

Downside

This will re-render the page and then scroll to the anchor.

UPDATE

A better way is to add the following:

<a href="#tab1" onclick="return false;">First Part</a>
Brian
  • 4,958
  • 8
  • 40
  • 56
1

Get your scrolling feature easily. It also supports Animated/Smooth scrolling as an additional feature. Details for Angular Scroll library:

Github - https://github.com/oblador/angular-scroll

Bower: bower install --save angular-scroll

npm : npm install --save angular-scroll

Minfied version - only 9kb

Smooth Scrolling (animated scrolling) - yes

Scroll Spy - yes

Documentation - excellent

Demo - http://oblador.github.io/angular-scroll/

Hope this helps.

Aakash
  • 21,375
  • 7
  • 100
  • 81
1

See https://code.angularjs.org/1.4.10/docs/api/ngRoute/provider/$routeProvider

[reloadOnSearch=true] - {boolean=} - reload route when only $location.search() or $location.hash() changes.

Setting this to false did the trick without all of the above for me.

T J
  • 42,762
  • 13
  • 83
  • 138
geekdenz
  • 759
  • 1
  • 5
  • 8
1

Based on @Stoyan I came up with the following solution:

app.run(function($location, $anchorScroll){
    var uri = window.location.href;

    if(uri.length >= 4){

        var parts = uri.split('#!#');
        if(parts.length > 1){
            var anchor = parts[parts.length -1];
            $location.hash(anchor);
            $anchorScroll();
        }
    }
});
ThatMSG
  • 1,456
  • 1
  • 16
  • 27
1

Try this will resolve the anchor issue.

app.run(function($location, $anchorScroll){
    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener('click', function (e) {
            e.preventDefault();

            document.querySelector(this.getAttribute('href')).scrollIntoView({
                behavior: 'smooth'
            });
        });
    });
});
Suhail Ahmed
  • 350
  • 3
  • 4
0

On Route change it will scroll to the top of the page.

 $scope.$on('$routeChangeSuccess', function () {
      window.scrollTo(0, 0);
  });

put this code on your controller.

Praveen M P
  • 11,314
  • 7
  • 34
  • 41
0

In my mind @slugslog had it, but I would change one thing. I would use replace instead so you don't have to set it back.

$scope.scrollTo = function(id) {
    var old = $location.hash();
    $location.hash(id).replace();
    $anchorScroll();
};

Docs Search for "Replace method"

Jackie
  • 21,969
  • 32
  • 147
  • 289
0

None of the solution above works for me, but I just tried this, and it worked,

<a href="#/#faq-1">Question 1</a>

So I realized I need to notify the page to start with the index page and then use the traditional anchor.

windmaomao
  • 7,120
  • 2
  • 32
  • 36
0

I'm using AngularJS 1.3.15 and looks like I don't have to do anything special.

https://code.angularjs.org/1.3.15/docs/api/ng/provider/$anchorScrollProvider

So, the following works for me in my html:

<ul>
  <li ng-repeat="page in pages"><a ng-href="#{{'id-'+id}}">{{id}}</a>
  </li>
</ul>
<div ng-attr-id="{{'id-'+id}}" </div>

I didn't have to make any changes to my controller or JavaScript at all.

T J
  • 42,762
  • 13
  • 83
  • 138
Digant C Kasundra
  • 1,606
  • 2
  • 17
  • 27
0

Sometime in angularjs application hash navigation not work and bootstrap jquery javascript libraries make extensive use of this type of navigation, to make it work add target="_self" to anchor tag. e.g. <a data-toggle="tab" href="#id_of_div_to_navigate" target="_self">