11

We have a project which uses Angular, but only for the UI binding/AJAX aspect of it, not for any sort of routing or SPA functionality.

We want to be able to use anchor links (#section-2) in articles we write within the CMS we have chosen, as well as use anchor links from other pages (/my-page#section-C), but Angular rewrites these to #/section-2, which breaks the anchor links that the CMS sets up.

It is not possible to augment the CMS to modify how anchor links are handled.

Is it possible to either:

  1. Remove the hashchange event binding from within Angular? I see that this event is attached to in the source file src/ng/browser.js where it handles some of the routing and link rewriting, but it's inside of a closure so it cannot be accessed directly (and we are linking to Angular from a CDN so it is not possible to modify the Angular source, plus we don't want to have to maintain our own "custom" Angular source).

  2. Set an option or call a configuration method which ultimately disables the entire routing aspect of Angular and prevents it from rewriting any sort of links? (Or, is there a way to not include this portion of Angular, but still retain the controller/UI binding/AJAX functionality?)

Note that I have already tried this:

$locationProvider.html5Mode(true)

However it renders all other links on the site inoperable because all links are passed through Angular for processing. So if I link to the homepage (<a href="/">Home</a>) and click the link with html5mode on, the link does nothing.

Ben Harrison
  • 2,121
  • 4
  • 24
  • 40
qJake
  • 16,821
  • 17
  • 83
  • 135
  • I am using Angular in ASP.NET project just for UI binding, don't setup any routes and don't use ngRoute module. Any links work correctly – Mikalai Jun 23 '16 at 20:15
  • @Mikalai I am not using any routing, nor am I using ngRoute. All I have done is include `angular.min.js` into my site, and I am trying to use anchor links, and Angluar is rewriting them. – qJake Jun 24 '16 at 04:45

6 Answers6

3

I believe that you want $anchorScroll. See this related answer: How to handle anchor hash linking in AngularJS

Here is an example of how it would work. The hash is just treated as part of the id:

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

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

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

See plunker demonstrating $anchorScroll

With routing, you can change the link to:

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

and add this to the run configuration:

 $rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
    if($location.hash()) {
        $anchorScroll();  
    }
 });

See plunker demonstrating scrolling with routing and $anchorScroll

Community
  • 1
  • 1
ScottL
  • 1,755
  • 10
  • 7
  • The problem with this (and the reason it won't work) is that the CMS we have integrated with only creates anchor links in the traditional HTML manner. Your example (`Foo`) will not work, as the CMS's WYSIWYG editor cannot produce anchor links in this manner. **The anchor link format must not change.** – qJake Jun 24 '16 at 04:42
  • And in fact, I hooked into `$routeChangeStarted`, `$routeChangeSuccess`, and `$routeChangeError`, and none of those events fired from within the controller that I have on that page. I do have `$rootScope` as a dependency being pulled in to my controller. – qJake Jun 24 '16 at 15:06
  • Unless you're using ngRoute, hook into the [$location events](https://docs.angularjs.org/api/ng/service/$location) – n daniel Jun 29 '16 at 13:51
  • I want to be very clear here: **This did not solve my problem.** It should not be awarded the bounty, but by StackOverflow's bounty rules, it will be. – qJake Jun 30 '16 at 18:10
2

As somewhat of a workaround (and certainly not best practice), I ended up modifying the Angular source in order to remove the URL rewriting.

I made a few changes, but I believe the one that caused anchor links to work again was adding a return; statement on Line 844 of location.js in the Angular source:

https://github.com/angular/angular.js/blob/master/src/ng/location.js#L844

This short-circuits around much of the URL rewriting functionality.

I also completely removed Lines 262-264 of browser.js, which removes Angular's hook on the hashchange event:

https://github.com/angular/angular.js/blob/master/src/ng/browser.js#L262-264

This didn't seem to affect any of the binding features of Angular, but it did cause anchor links to start working again.

qJake
  • 16,821
  • 17
  • 83
  • 135
2

Angular's $locationProvider.html5Mode function with it's rewriteLinks setting is what you're looking for. It will put angular into a mode where $location can still be used to read and write the browser's url, but it won't ever attempt to hijack link clicks and trigger angular's SPA routing.

eg:

  $locationProvider.html5Mode({
      enabled: true,
      requireBase: false,
      rewriteLinks: false 
  });
Tyson
  • 14,726
  • 6
  • 31
  • 43
  • I didn't see that option when I was looking in the documentation. It's possible we are on an older version of Angular that doesn't include this option. You should have answered this when the bounty was active! :) – qJake Jul 06 '16 at 15:49
  • @qJake Haha, I only just had this problem myself last night. Was about to head down the same path as you (editing angular source) but found another SO answer that pointed me to these docs. Very hidden, nothing else references it apart from that 1 little comment inside the html5mode method docs. It's been there since 1.3 apparently, but I never knew about it either. – Tyson Jul 07 '16 at 01:05
  • @qJake You can always change the accepted answer to mine ;) – Tyson Jul 07 '16 at 01:06
0

After setting $locationProvider.html5Mode(true), did you also set a <base> in the <head> of your document?

<html>
  <head>
    <base href="/">
  </head>
</html>

Have a look here:

"There are 2 things that need to be done.

  1. Configuring $locationProvider
  2. Setting our base for relative links

or here

Setily
  • 814
  • 1
  • 9
  • 21
0

I had a similar problem when using angular with wordpress and found a really simple fix. All you have to do is remove all $location injection in your controllers. When you inject $location service (whether you use it or not) it hijacks the # behaviour.

This means that you have to scour through all your controllers, directives etc and make sure that the $location service is not injected anywhere.

HTH

utxin
  • 64
  • 7
-2

But why not use ng-route? it would totally solve your issues. using Ng routes you can achieve the following (which is what you want).

<a href="http://www.example.com/base/#section-A"> Section A</a>

<a href="http://www.example.com/base/my-page#section-C">Another page</a>

<a href="/other-base/another?search">external</a>

here is the app.js snippet

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

Html link rewriting

When you use HTML5 history API mode, you will not need special hashbang links. All you have to do is specify regular URL links, such as: <a href="/some?foo=bar">link</a>

When a user clicks on this link,

In a legacy browser, the URL changes to /index.html#!/some?foo=bar

In a modern browser, the URL changes to /some?foo=bar

In cases like the following, links are not rewritten; instead, the browser will perform a full page reload to the original link.

Links that contain target element Example: <a href="/ext/link?a=b" target="_self">link</a>

Absolute links that go to a different domain Example: <a href="http://angularjs.org/">link</a>

Links starting with '/' that lead to a different base path Example: <a href="/not-my-base/link">link</a>

Relative links

Be sure to check all relative links, images, scripts etc. Angular requires you to specify the url base in the head of your main html file (<base href="/my-base/index.html">) unless html5Mode.requireBase is set to false in the html5Mode definition object passed to $locationProvider.html5Mode(). With that, relative urls will always be resolved to this base url, even if the initial url of the document was different.

There is one exception: Links that only contain a hash fragment (e.g. <a href="#target">) will only change $location.hash() and not modify the url otherwise. This is useful for scrolling to anchors on the same page without needing to know on which page the user currently is.

Server side

Using this mode requires URL rewriting on server side, basically you have to rewrite all your links to entry point of your application (e.g. index.html). Requiring a tag is also important for this case, as it allows Angular to differentiate between the part of the url that is the application base and the path that should be handled by the application.

ngSource

Bamieh
  • 10,358
  • 4
  • 31
  • 52