53

I have a working Angular.js app with HTML5 mode enabled.

$location.Html5mode(true).hashbang("!");

What I want to achieve is to get some URLs or <a> tags to do the normal browsing behaviour instead of changing the URL in the address bar using HTML5 history API and handling it using Angular controllers.

I have this links:

<a href='/auth/facebook'>Sign in with Facebook</a>
<a href='/auth/twitter'>Sign in with Twitter</a>
<a href='/auth/...'>Sign in with ...</a>

And I want the browser to redirect the user to /auth/... so the user will be then redirected to an authentication service.

Is there any way I can do this?

Vlad V
  • 1,594
  • 1
  • 13
  • 28

7 Answers7

120

Adding target="_self" works in Angular 1.0.1:

<a target="_self" href='/auth/facebook'>Sign in with Facebook</a>

This feature is documented (https://docs.angularjs.org/guide/$location - search for '_self')

If you're curious, look at the angular source (line 5365 @ v1.0.1). The click hijacking only happens if !elm.attr('target') is true.

Matthew Haugen
  • 12,916
  • 5
  • 38
  • 54
Nik
  • 5,801
  • 2
  • 31
  • 23
  • works with angular 1.2.16 and likely with current master https://github.com/angular/angular.js/blob/master/src/ng/location.js#L687 – Patrick Refondini Jun 22 '14 at 17:04
  • 1
    had to use this to enable a transitioning period while converting rails routes to angular routes – Mikey Oct 03 '14 at 11:18
  • 1
    This causes a full page reload for me when using Angular in conjunction with Turbolinks. – a2f0 Dec 29 '14 at 21:03
20

An alternative to Fran6co's method is to disable the 'rewriteLinks' option in the $locationProvider:

$locationProvider.html5Mode({
    enabled: true,
    rewriteLinks: false
});

This will accomplish exactly the same thing as calling $rootElement.off('click'), but will not interfere with other javascript that handles click events on your app's root element.

See docs, and relevant source

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
ZMan9854
  • 331
  • 2
  • 3
  • This doesn't work for me, but @Fran6co's [solution](http://stackoverflow.com/a/11785196/295686) almost does (see my comment). I'm running it on a page that loads the app but no controller targets the tree that the anchor tag is in. – mlhDev Sep 15 '15 at 21:01
  • 1
    +1 for this answer. It wasn't until I enabled html5Mode on $locationProvider that the issue arose in the first place. – Ryan Francis Jan 27 '16 at 16:04
16

This is the code for turning off deep linking all together. It disables the click event handler from the rootElement.

angular.module('myApp', [])
   .run(['$location', '$rootElement', function ($location, $rootElement) {
      $rootElement.off('click');
}]);
Fran6co
  • 173
  • 6
2

To work off the Nik's answer, if you have lots of links and don't want to add targets to each one of them, you can use a directive:

Module.directive('a', function () {
    return {
        restrict: 'E',
        link: function(scope, element, attrs) {
            element.attr("target", "_self");
        }
    };
});
Dragonfly
  • 4,261
  • 7
  • 34
  • 60
1

I've run into the same issue a few times now with angular, and while I've come up with two functional solutions, both feel like hacks and not very "angular".

Hack #1:

Bind a window.location refresh to the link's click event.

<a 
  href=/external/link.html 
  onclick="window.location = 'http://example.com/external/link.html';"
>

The downside and problems with this approach are fairly obvious.

Hack #2

Setup Angular $routes that perform a $window.location change.

// Route
.when('/external', {
  templateUrl: 'path/to/dummy/template', 
  controller: 'external'
})

// Controller
.controller('external', ['$window', function ($window) {
  $window.location = 'http://www.google.com';
}])

I imagine that you could extend this using $routeParams or query strings to have one controller handle all "external" links.

As I said, neither of these solutions are very satisfactory, but if you must get this working in the short term, they might help.

On a side note, I would really like to see Angular support rel=external for this type of functionality, much like jQueryMobile uses it to disable ajax page loading.

Noah Freitas
  • 17,240
  • 10
  • 50
  • 67
  • I would like to see `rel=external` working too. Until then, I will use the **hack #2**. I will mark your answer as accepted shortly but I would like to wait a little longer to see if anyone solved this in a more _angular-ish way_. Thank you! – Vlad V Jul 20 '12 at 15:48
  • I had similar ideas, specifically using `$window.location` to force a page refresh in `html5mode = true`—this does not work properly in Webkit browsers; definitely use `target=_self`. – jlmakes May 03 '13 at 13:47
0

In your routes try:

$routeProvider.otherwise({})

T J
  • 42,762
  • 13
  • 83
  • 138
Jason Als
  • 723
  • 4
  • 12
0

To add to Dragonfly's answer, a best practice I have found to limit the number of target="_self" attributes is to never put the ng-app attribute on the body tag. By doing that you are telling angular that everything within the body tags are a part of the angular app.

If you are working within a static wrapper that should not be affected by angular, put your ng-app attribute on a div (or other element) that surrounds only the location your angular app is going to be working in. This way you will only have to put the target='_self' attribute on links that will be children of the ng-app element.

<body>
    ... top markup ...
    <div ng-app="myApp">
        <div ng-view></div>
    </div>
    ... bottom markup ...
</body>
risingfish
  • 354
  • 3
  • 9