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?

7 Answers 7

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.

Sign up to request clarification or add additional context in comments.

3 Comments

works with angular 1.2.16 and likely with current master github.com/angular/angular.js/blob/master/src/ng/…
had to use this to enable a transitioning period while converting rails routes to angular routes
This causes a full page reload for me when using Angular in conjunction with Turbolinks.
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

2 Comments

This doesn't work for me, but @Fran6co's solution 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.
+1 for this answer. It wasn't until I enabled html5Mode on $locationProvider that the issue arose in the first place.
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');
}]);

5 Comments

This is a nice solution for a mixed MVC/Angular site where some links are to a mini-SPA which are intercepted by the Angular route config, yet other links are for regular MVC page requests. In my case the regular links worked until an Angular controller was loaded (when SPA was used), at which point the regular MVC links were hijacked and produced no response. So upvoted.
Whatever you put $location in you'll need to do this: e.g. a .controller('fooController', function($location) { }) you'll need to remove events like .controller('fooController', function($element, $location) { $element.off('click'); }).
Thanks, I'm using pushstate to set the url when applying filters on a page. With this I can use $location.search() and still have my <a> tags function normally. (without having to apply target="_self" everywhere)
I ran into this with a mixed Angular and Node/Express app and this worked like a charm. Thanks!
This initially worked but upon further testing it created unexpected side effects. On a mixed Angular/pure jQuery page, a <a href=#> (outside an angular controller) with a jQuery e.preventDefault() used to work but now I encountered an error very similar to this which sounded right - "$locationWatch expects the $location service to be updated first, followed by the browser. But when I pushState to the browser directly [or in my case navigate to #], the $location service still has the old url." I had to continue looking for alternatives.
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");
        }
    };
});

Comments

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.

2 Comments

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!
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.
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>

Comments

0

In your routes try:

$routeProvider.otherwise({})

1 Comment

Doesn't work. Only if I use the External controller from Noah's answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.