angular.js link behaviour - disable deep linking for specific URLs
Asked Answered
T

7

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?

Toper answered 20/7, 2012 at 13:6 Comment(0)
R
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.

Reconstruct answered 8/8, 2012 at 4:56 Comment(3)
works with angular 1.2.16 and likely with current master github.com/angular/angular.js/blob/master/src/ng/…Goulette
had to use this to enable a transitioning period while converting rails routes to angular routesPorter
This causes a full page reload for me when using Angular in conjunction with Turbolinks.Sesquiplane
H
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

Howze answered 19/2, 2015 at 3:18 Comment(2)
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.Ignace
+1 for this answer. It wasn't until I enabled html5Mode on $locationProvider that the issue arose in the first place.Choler
D
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');
}]);
Deweese answered 2/8, 2012 at 20:26 Comment(5)
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.Defroster
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'); }).Builtup
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)Breve
I ran into this with a mixed Angular and Node/Express app and this worked like a charm. Thanks!Insane
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.Ignace
B
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");
        }
    };
});
Barrator answered 10/9, 2014 at 18:22 Comment(0)
B
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.

Bombardier answered 20/7, 2012 at 15:39 Comment(2)
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!Toper
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.Hoe
D
0

In your routes try:

$routeProvider.otherwise({})

Dornick answered 20/7, 2012 at 20:57 Comment(1)
Doesn't work. Only if I use the External controller from Noah's answer.Toper
S
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>
Senseless answered 14/10, 2014 at 23:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.