Simply injecting $location anywhere in your app breaks anchor links (which up till now have been working for 20 years!). Here is example code demonstrating the issue: https://gist.github.com/skivvies/7125406
Here is a rawgithub link so you can actually run this code: https://rawgithub.com/skivvies/7125406/raw/b42a135b7a40db7dc00a7726fa8efd080e14b7b7/index.html
Please keep an eye on the browser's address bar when you click "link to anchor". Compare this to almost the exact same code, with the only difference being that $location is not injected: https://gist.github.com/skivvies/7125477
Again, here is a rawgithub link to this version so you can run it: https://rawgithub.com/skivvies/7125477/raw/1b4a4d2692616c42b92b813b4c7d1e26ffe38c9b/index.html
Reproduction steps:
(I hit this because I'm using angular-bootstrap's dropdown toggle directive which injects $location, I don't ever actually inject it into my app, but simply depending on angular-bootstrap is enough to break my anchor links. Thanks to this post for tipping me off. /cc @pkozlowski-opensource)
http://www.benlesh.com/2013/02/angular-js-scrolling-to-element-by-id.html looked like a promising workaround but ultimately didn't work. Update: See comments below.
i am also seeing this issue.
seeing the exact same behavior.
Might have some time to hack on a PR for this coming up. Any pointers from the core team on this one before I dive in?
Noticed some other $location-related bug fixes have gone into the last couple point releases, nice! I'll try to spend some time on this fix this weekend. Angular team, please ping me if that's not a good idea or if you have any other input.
Here is a gist with target="_self": https://gist.github.com/skivvies/8430668
And here is a rawgithub link so you can try it:
https://rawgithub.com/skivvies/8430668/raw/e17b8cae60faaec3a07084d657c0eeaf47f8b3f3/index.html
When the link is clicked, the viewport is successfully scrolled to the
targeted anchor, but the url hash is changed to #/anchor rather than
you have a page in your history before this one, clicking the back button
won't take you to it as it should, but rather now does nothing but cause a
momentary flicker.)
So this isn't a complete workaround, but is still somewhat of an
improvement. Thanks for the tip!
On Tue, Jan 14, 2014 at 9:38 PM, Llyle van Schalkwyk <
[email protected]> wrote:
It seems that using target="_self" works around the issue.
—
Reply to this email directly or view it on GitHubhttps://github.com/angular/angular.js/issues/4608#issuecomment-32329769
.
It's probably because, when not in html5Mode, the $location is assuming that you're using Angular's routing and treating all "#" links as such.
So there are two options/workarounds:
ng-click
to update the $location.hash
and call $anchorScroll
in a $timeout
, it seems to fix the issue: http://plnkr.co/edit/nzTBmWFKOMVkxrcJSiPW?p=previewhtml5Mode
, and that also solves the issue: http://plnkr.co/edit/qAH0bPAMdUKRYblW6I6k?p=previewI guess what I'm saying is, that at the point that $location is being used, if you're not in HTML5 mode, everything after the "#" is Angular routing's domain, and it's going to behave differently than what you might expect.
Thanks for the comments, @blesh.
I think the first proposed workaround of changing every href to an ng-click which updates $location.hash and calls $anchorScroll in a $timeout is ugly. It requires undoing your valid html for an unrobust attempt at reimplementing the correct behavior in javascript. Given this has been working in browsers -- without needing javascript -- for the last 20 years, I think an acceptable workaround would have to not go against the grain of the web so drastically. Let's not make Tim Berners-Lee want to come kill us in the night!
As for the second proposal, I tested it, and it's not sufficient to just set html5Mode to true, you also have to update all your anchor links to add target="_self" for it to work, as you've done in your plunker. This, at least, is a complete workaround (i.e. with only target="_self" and no html5Mode, the url hash would incorrectly be prefixed with /, as noted earlier), and doesn't break the web the way the first proposal does. It's too bad you have to update every anchor link in your application with some magic markup that looks like it doesn't accomplish anything to someone else who looks at the code (or yourself, 3 months later), but IMO it's far better than living with the bug or using the first proposal.
Thanks again for the comments. And still hoping to take a crack at a patch some time if no one else beats me to it.
Just wanted to add, another metric for a workaround is what you would do once the bug being worked around is actually fixed. With the first proposal, to benefit from the bug being fixed, you'd have to change all the ng-clicks back to proper hrefs. With the second, you could still benefit from the fix without having to take out all the target="_self" attributes, which could be done at a later time if there was more pressing work to do.
I'm not sure how this could be patched without a large refactor of routing, which might be messy. Best of luck.
I could be wrong of course, I haven't looked too closely at that code.
Okay, so there's one more workaround...
<a href="#/#anchor">Go to the id, "anchor"</a>
I got to thinking about it, and I realized, $location is only to be used with routing, really, and when you're routing you can scroll to ids on a page with links by a "hash" on the route. so the link url is like http://hostname/path/to/app/#/angular/route/#hashOnPage
, which is the same as /path/to/app/#/angular/route/#hashOnPage
where hashOnPage
is the ID of what you want to scroll to. Depending on where you're at, where the app lives, and what route your on in it, you can use that, plust a second #
to add a hash to something on the screen... If that makes sense.
Does that more effectively solve your problem?
In the end, you really shouldn't use $location
unless you're using routing. Since that's sort of what it's built around.
Thanks for the additional idea, @blesh. In the case where I hit this, I wasn't using routes, and had no need for $location at all. I was simply using angular-bootstrap for something totally unrelated to routing. However, since one of _its_ directives used $location, it spread like a virus, breaking anchor links not just within my Angular app, but even to ones outside the DOM element with ng-app applied. Ideally the area affected by using $location would be more constrained, so it wouldn't affect code in far-removed and unsuspecting places.
So your second workaround is still the best fit for this case, where you have no need for $location or routing, but some dependency using it is messing up your anchor links.
It looks like angular-bootstrap is no longer using $location as of angular-ui/bootstrap@35c93076 (which just might also fix angular-ui/bootstrap#619 -- see how $location's current behavior can cause bugs like this everywhere?), so for other angular-bootstrap users who don't need $location themselves (show of hands?), upgrading to the latest version should also get rid of this bug, as long as nothing else they touch uses $location.
Thanks also for your warnings that fixing this would entail a large refactor of routing, which may be messy. If that's true, it probably makes sense for a core developer to chime in on this thread before a less experienced community contributor spends a lot of time on a patch.
any updates on resolving this? I also have module dependencies that include $location so all my anchor links are broken.
Still hoping to hear from a core dev before taking a shot at a PR since it sounds like it will require coordination to get merged.
Also just noticed that the milestone was changed from "1.2.x" to "backlog" at some point. Does that mean this won't make it into a release any time soon? /cc @btford
@skivvies, the backlog milestone really just means we aren't actively pushing to get it into the next release, but if anyone in the community cares to investigate and provide more information about this, we can make a decision about how serious the issue is and prioritize it.
A good start with this is to provide a test in angular core which fails, reproducing the issue in a minimal fashion. Otherwise, provide a reproduction of the issue on plnkr or jsbin or something. I think in an issue tracker like chromium you'd label this "unconfirmed", we really need to see that this isn't working as expected.
(This is just my personal opinion and does not necessarily reflect the opinion of the Angular team)
fyi this is my workaround. back links still work, and no need to change my html.
the only issue is that it creates verbosity in the querystring. ex: mySite.com/page.html#/anchor#anchor
put inside of $scope-on-location-change-start:
if($location.hash() && $location.hash().length>=1)
{
var path = $location.path();
var potentialAnchor = path.substring(path.lastIndexOf("/")+1);
if ($("#" + potentialAnchor).length > 0) {
$location.hash(potentialAnchor);
$anchorScroll();
}
}
edit: and i tested it down to ie8, still works there.
@caitp wrote:
A good start with this is to provide a test in angular core which fails, reproducing the issue in a minimal fashion. Otherwise, provide a reproduction of the issue on plnkr or jsbin or something. I think in an issue tracker like chromium you'd label this "unconfirmed", we really need to see that this isn't working as expected.
Sounds like you may have missed the ticket description. You'll see links there to a minimal reproduction. This issue is definitely not unconfirmed. I believe @btford confirmed it, and was the one who originally slated it for 1.2.x.
Submitting a PR with a failing unit test is a great idea though. I spent a lot of time preparing the description of the problem already. Can anyone else step up and write the test?
Thanks.
@jasons-novaleaf wrote:
fyi this is my workaround. back links still work, and no need to change my html. the only issue is that it creates verbosity in the querystring. ex: mySite.com/page.html#/anchor#anchor
If you use this workaround, unless you want to break your links after the issue is fixed (or possibly write legacy code to redirect them back), you'll have to commit to continuing to use these verbose query strings even after the issue is fixed. So I prefer the workaround of adding target="_self" to anchor links and making sure html5Mode is true. That way you get nice, clean, normal-looking anchor links both before and after the fix. (And the back button still works.)
Thank you for sharing, though!
@skivvies, actually I've been paying attention to this thread for some time now, and I still haven't really been convinced of anything truly confirming this. I still can't decide if this is an application error or not, which is why I say I need to see a more minimal, exact reproduction.
But yeah, the reason you aren't seeing a PRs plz! status is generally because this hasn't been shown to be a real problem yet. If we know for sure that this is an issue, then we'll definitely be saying "please contribute a fix for this, we might not be working hard to get this into the next release, but if someone comes up with a fix that doesn't break other things badly, they're probably good"
Again, I'm paraphrasing, but this is really what I've been seeing from this issue for the past few months.
Wow, ok, this is totally coming as a surprise to me. https://gist.github.com/skivvies/7125406 looks to me like as minimal a reproduction as you can get. And the behavior when you run that seems incontrovertibly broken.
I don't know how this could be any more minimal or any more clearly broken, but if you could help me to understand, I'd be happy to do more to make a clearer case.
At the very least, would you agree the behavior of https://gist.github.com/skivvies/7125406 is unexpected enough to warrant some documentation warning users about it?
Take out $location and it's fine, add it back in (or depend on something (that depends on something...) that adds it in), and all of a sudden your app's anchor links are broken, and you probably have no idea why. This has clearly bitten all the people that chimed in here, and seems to me like the definition of a subtle and undesired bug.
I guess you could say it's a bug not to instantiate $location on bootstrap, but that's sort of unrelated
Yes it is, since you'll still see the bug if you instantiate $location on
bootstrap. So not doing so makes for a more minimal reproduction.
On Fri, Feb 7, 2014 at 1:19 PM, Caitlin Potter [email protected]:
I guess you could say it's a bug not to instantiate $location on
bootstrap, but that's sort of unrelated
Reply to this email directly or view it on GitHubhttps://github.com/angular/angular.js/issues/4608#issuecomment-34482945
.
Instantiating location on bootstrap wouldn't make the link to the named anchor work as expected either, though, because the rewritten URL falls within the SPA and prevents the default behaviour. In html5mode we would see the hash fragment correctly (the talks about melding $anchorScroll into $location could fix this), but it would work inconsistently from hashbang mode, which is why that is sort of problematic
Instantiating location on bootstrap wouldn't make the link to the named anchor work as expected either
Right, that's what I meant by "you'll still see the bug if you instantiate $location on bootstrap", but thanks for the additional info and clarity. Good to make it clearer that the workaround that requires setting html5Mode to true won't work on browsers that don't support html5Mode.
Interesting there's talk of merging $anchorScroll and $location, hadn't heard that.
This should really be fixed. Didn't realizing adding angular bootstrap to my site would break our site navigation. I can't seem to figure out a workaround either.
fyi, seems like my workaround only half-works with back-buttons. not sure why.. anyway. yeah this should be fixed :(
Alright, I'll try to put together a fix for 1.3, but I'm not sure how difficult this will be to fix, so...
@caitp Awesome, thank you! After you dive in, if you see how it should be done but don't immediately have time to do it, if you leave an outline of the way you want it fixed, maybe I or another community member will have time to submit the PR.
@shawn-simon et al. Thanks so much for chiming in in support of this issue. Re your workaround, I think ideally a workaround would not require changing any "href" attributes or anything else that you'd just have to change back again once the fix lands. So I think the target="_self" + html5mode workaround is still the best (if you can afford to not support pre-html5 browsers).
I think that step 1) is ensuring that $location is injected on bootstrap, so that behaviour of the library is consistent.
Step 2) is treating anchor tags differently (IE, if HREF starts with a '#', ignore it).
If this doesn't work, basically merging $anchorScroll into $location needs to happen, and there has been some discussion about this already.
I don't totally know how much work it will be to get this working, but we'll see.
an idea: you can keep the current functionality the same (to avoid side-effects) but fix this issue for people like us by doing something like:
1) check if the querystring starts with the $location.hashPrefix() value
2) if it doesn't, then ignore it.
3) if it does, then do the current workflow.
so people like me who set
$location.hashPrefix('!');
will have anchor links that work. where as people who use the default hashPrefix (empty) will see the current behavior.
Its not going to be merged until 1.3.x (so breaking changes are ok), and I want the behaviour to be consistent. Ideally, path links should work in hashbang mode, so just starting with #
should be enough to say ignore it
ng-include requires, but optionally uses $anchorScroll, which pulls in $location. This means that just by using ng-include once, i now need to rewrite all my anchor tags.
That is broken, or at least absurdly obtuse behavior. More than happy to help. Let me know if more people bashing on it would help.
Hi @caitp,
Here is a fork of the original gist demonstrating the bug, with the latest angular snapshot (1.3.0-build.2608+sha.49e7c32) replacing angular 1.2.9, and the bug is still happening:
https://rawgit.com/skivvies/11025788/raw/38e62189d6072c6cb83b049582a833a62bf5d747/index.html
It looks like the changes from #6899 you mentioned would have landed in that snapshot, but please let me know if I should have tested against a different version of angular.
Thanks, and encouraged that there might be some more progress on this.
@skivvies thanks for answering! Unfortunately the work on it isn't quite done yet :( There's an adendum to this at #7152. I think the tests should pass now in it, and it should work better with modern browsers now.
You might want to try that patch and see if it solves this (with html5Mode enabled). Still not positive that it will work, because I think we are still preventing the default behaviour and not asking $anchorScroll to do its thing. So it will probably not fix anchor links. But it should be a lot simpler to make those work now, at least for html5Mode.
It would be great if the AngularJS team could put a note about this issue in the documentation. I just lost a day going through my rewrite rules trying to figure out why my links were broken before I realized it was related to angular.
I feel your pain @halleycarleton. That's exactly what I was afraid of with this issue. Three months ago I said:
At the very least, would you agree the behavior of https://gist.github.com/skivvies/7125406 is unexpected enough to warrant some documentation warning users about it?
But further discussion never indicated a documentation patch would be accepted. @caitp, since it's unclear how soon this will be fixed, would you accept a documentation patch about this bug in the meantime?
What is the best way to verify I'm being hit by this? I've actually got to use $location in my app's run block, and am also using Angular Bootstrap. I've tried updating to 0.11 of Bootstrap and even temporarily commenting out my use of $location, but no dice.
Back button works... occasionally for me. It wasn't working when I first began work on the app (this is my first Angular app) and it started working suddenly at one point with no conscious or attributable change from me. Obviously I changed something, but know not what. In the last few weeks it seems to have gotten fouled up again.
I can't find help anywhere on getting back button working in my situation. Don't know what to attribute the breaking to. I guess soon I'll starting hunting at a ridiculous detail level, but there are no red flags that I can see...
FWIW, we are also stuck on 1.2, so porting to 1.3 is not a valid fix for us.
Is the issue fixed in 1.3.x? IIRC it also has this issue.
Hit by this bug. all the non-angular links breaks. Is there any on going plan to fix it?
To deal with this problem I use a directive that changes anchor links to full urls, maybe someone finds it useful: https://gist.github.com/dcadenas/566434ba0af9f3eabb07
There's a PR for this, here's a working demo http://plnkr.co/edit/WVPYzaXOKujcb5vSbvJz?p=preview
It's not perfect (it only works for relative hash fragment links), but it's a good start --- feel free to lend your thoughts
I don't think this is limited to just anchor links interactivity. If I try to just refresh example.com#param
, it will also reset to example.com#/param
.
@caitp where is that PR? How is that going?
K. I'm working on a small documentation note for https://docs.angularjs.org/guide/$location right now - think that's necessary, or should I hold off?
I mean, you could work on it --- you should see if that CL I linked fixes the first issue you're talking about though, because I have my doubts that it would (at least in hashbang mode) --- the reverted commits only really affect clicking on anchor links
@caitp Submitted a PR, if you want to check it briefly. I checked your branch - worked fine for my problem, thanks for that. Looking forward to seeing that merged in.
Ug... Forgot to turn off html5mode. Doesn't work if that's disabled. Still needs some work. :/
yeah I think that's a separate bug
Aight. Shall I open a ticket for that?
sure
Is there any update on this bug, and or is the core team still open to a PR to fix this functionality (even tho this is a wildly breaking change)?
@samccone, @tbosch provided a fix for this.
@caitp is there a link to the resolved solution? I must be missing it. Thanks
22948807e324eb0b182b15b31045dc306a9f3231 (and a bunch of other related commits) are the big fix
Wait, which bug are we talking about? That's the "big" location bug, but there are lots of other ones as well, many have been fixed
ah yes it is fixed there, just waiting on the 1.2.x release of that.
Thanks!
Oh! >.< I spend 2 days figuring out why it didn't work. I am glad this is also coming to 1.2.x :+1: . Any idea when it is going to be released though?
set target="_self" in every anchor tag.This workaround is working fine for me.
set target="_self" fix my problem. thx :)
setting target="_self" only fixes the problem internally to the site itself. If you want to link from outside to an anchor link, there is still that horrible slash (angular's hijacking) sitting right inside your hyperlink. Its terrible!
I'm using version 1.3.9 and it doesn't work yet. Why are folks saying it was fixed in 1.2??
@wobine We didn't say it was fixed in 1.2 . It has been fixed and it will probably be merged into 1.2.x but that hasn't happened yet.
I managed to get anchors working correctly using the following setup (it only considers id anchors and probably messes up angular routing for more complex use cases)
module.config([ '$anchorScrollProvider', function($anchorScrollProvider) {
$anchorScrollProvider.disableAutoScrolling();
}])
.run(['$rootScope', '$location', function($rootscope, $location) {
// mimic the anchor behaviour, avoid using the $anchorScroll service
$rootscope.$on('$locationChangeSuccess', function() {
var element = $('#' + $location.url().replace('/', ''));
if(element.length > 0) element[0].scrollIntoView();
});
}]);
Hi,
I've found a solution to this nightmare, but I'm new to AngularJS and don't know if it is a correct solution.
I've enabled html5mode like this:
saApp.config (function($locationProvider) {
$locationProvider.html5Mode({
enabled : true,
requireBase: false,
rewriteLinks : false
});
});
Now all links work as expected and there are no errors on js.
I got around this by using a ng-click event with a window.open(url) command:
<a class="viewLink" href ng-click="linkViewed()">
$scope.mediaViewed = function linkViewed() {
window.open($scope.link)
}
I can confirm that manutalcual answer works as expected with AngularJS v1.3.13.
The only problem is that when I directly load e.g. http://site.com/#test and I click again on #test the final URL is http://site.com/#test#test.
@manutalcual, your solution solved it.
Another +1 for @manutalcual's solution. Given the implication of angular-bootstrap, it's also probably worth noting that I'm using 0.13.0 of that, which includes this change mentioned in the conversation above.
This is my workaround:
Stop event propagation directive with high priority
var app = angular.module('my.module', []);
app.directive('externalAnchorLink', [function () {
return {
restrict: 'A',
priority: 0,
link: function (scope, el) {
/*jslint unparam: true*/
el.on('click', function (event) {
event.stopPropagation();
});
}
};
}]);
Then in HTML
<a href="/test/example/#my-anchor" external-anchor-link>link text</a>
Regarding the original problem ($location
affecting the default behavior of anchor links):
Indeed Angular uses #
(potentially with a hash-prefix after #
) to identify a "client-side" route (in non-HTML5 mode). It is $location
's job to interact with hash part of the URL. So, since it is not possible to distinguish a #some-client-side-route-path
for #i-want-to-scroll-to-an-element
, it has to always assume it is a client-side route path.
There are several solutions available (most of them discussed above) - especially if you don't use $location
/client-side routing in your app (but might have dependencies that instantiate it):
href="##i-want-to-scroll-to-an-element"
.$locationProvider
to use HTML5 mode#foo
is not recognized as a client-side route path. E.g. with $locationProvider.hashPrefix('!')
, a client-side route needs to start with #!
(e.g. #!foo
). Thus #foo
is not recognized as a path and the normal browser behavior applies.Note: Starting with v1.6 the default hash-prefix will be !
, so OP's usecase will work out-of-the-box (without the need to manually call $locationProvider.hashPrefix('!')
). In older versions, you need to configure it yourself.
I understand there may be several roblems discussed in this issue, but this does not help with assessing, investigating and hopefully fixing them, so (since the original problem falls under the _fixed_/_works as expected_ category), I am going to close this issue. If you are still running into different problems (that are not already reported), please open separate issues.
Most helpful comment
Hi,
I've found a solution to this nightmare, but I'm new to AngularJS and don't know if it is a correct solution.
I've enabled html5mode like this:
Now all links work as expected and there are no errors on js.