Using Angular 1.5 in an asp.net mvc site.
I have a base tag:
<base href="/fr/">
and html5mode is set.
The site works perfectly. I use ui-router... no issues.
But today I hit one and this is when I try to browse to this kind of url:
I get an error from angular:
url is undefined
trimEmptyHash@http://............/angular.js?tag=2016020919:12165:3
If I navigate to /fr/?arg=1 (with the trailing slash) the error goes away.
In this context, is this a bug? How to get the query string to work without a trailing slash? "Normal" people typing a url normally will never put a slash before the question mark...
Note: I don't know if this is a ui-router or an angularjs issue. I checked this and tried the 2 mentioned solutions but it didn't change anything (a breakpoint in the rule and I see the rule is not even called).
I traced into angular code, especially $$parseLinkUrl in the LocationHtml5Url function.
this.$$parseLinkUrl = function(url, relHref) {
if (relHref && relHref[0] === '#') {
// special case for links to hash fragments:
// keep the old url and only replace the hash fragment
this.hash(relHref.slice(1));
return true;
}
var appUrl, prevAppUrl;
var rewrittenUrl;
if (isDefined(appUrl = beginsWith(appBase, url))) {
prevAppUrl = appUrl;
if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
} else {
rewrittenUrl = appBase + prevAppUrl;
}
} else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
rewrittenUrl = appBaseNoFile + appUrl;
} else if (appBaseNoFile == url + '/') {
rewrittenUrl = appBaseNoFile;
}
if (rewrittenUrl) {
this.$$parse(rewrittenUrl);
}
return !!rewrittenUrl;
};
For a url like /fr?arg, appBase has a trailing slash and url has not, so rewrittenUrl is never set.
For a url like /fr/about?arg, appBase with its trailing slash is contained in the url, so rewrittenUrl is set.
I don't think you need a trailing / in your base URL. Have you tried removing that ?
Hm...omitting the trailing / will break relative URLs to assets, so it's not really useful.
One thing that seems to work is including the filename in baseHref (in fact including anything after the trailing /, such as xyz or even . seems to work - at least on Chrome, Firefox and Edge).
E.g. change <base href="/fr/" /> to:
<base href="/fr/index.html" /> or<base href="/fr/xyz" /> or even<base href="/fr/." />No it doesn't work. And anyway, after tracing into $$parseLinkUrl again, I don't see how it could work.
@cadilhac, you mean appending something after the / doesn't work ?
(It worked for me in a plnkr :confused:)
Yes I mean that. Can you show me such a plunker?
Also remember that this issue happens when a user types the url in the address bar of the browser, not when following an internal link to the base url containing a query string. How can you achieve such a test with a plunker?
@cadilhac, you are right, I missed that. Would it work if you configured your server to rewrite /fr as /fr/(as a workaround).
I'm not sure if ignoring the trailing slash might break other usecases.
/cc @petebacondarwin (who knows his way around $location :smile:)
Well, I am surprised that nobody met this case yet. It seems pretty usual to see a query string just afer the base url, no? Does it look like a bug in angular? Why doesn't $$parseLinkUrl handle this normal case?
I tried the rewriting and of course it fixed my issue. I hope it is only a temporary workaround though...
I should add that I'm not able to use my rewriting rule when in production because it tries to add a slash to everything. It breaks loading assets created by the Cassette minifier (asp.net MVC). It breaks ajax calls too. So, right now, I'm stuck.
I will take a look at this during next week OK?
@petebacondarwin of course this is ok :+1:
Hi @petebacondarwin. What is the status of this development?
Tomorrow or Monday I promise. Sorry
OK, so I have had a little play with this today...
The first thing I notice is that most http servers that provide a fallback rewrite for HTML5 history support will indeed redirect a request for /fr to /fr/, so I had to write a more simple HTTP server to actually recreate this problem.
The second thing, is that if your base href is /fr/ then this is equivalent to a base href of /fr/index.html, and that the URL of /fr is actually outside the base href. The URL specification makes no requirement that the two should be treated as equivalent. See http://tools.ietf.org/html/rfc3986#section-6.2.4
This is an unusual case, which most people do not hit because their servers are configured to redirect page requests that do not have a trailing slash to ones that do. I don't think that we can special case this in $location or even $browser since it could break URLs that do not expect to behave this way.
@cadilhac: The correct approach, I believe, is to set up redirects correctly for such URLs:
<html>
<head>
<base href="/fr/">
</head>
<body ng-app="app">
<a href="/fr?arg=2">Link</a>
<script>
function absoluteUrl(url) {
var urlParsingNode = document.createElement("a");
urlParsingNode.setAttribute("href", url);
return urlParsingNode.href;
}
function stripQuery(url) {
return url.replace(/\?[^?]*/, '');
}
var baseElement = document.getElementsByTagName('base')[0];
var baseUrl = absoluteUrl(baseElement ? baseElement.getAttribute('href') : '/');
var currentUrl = stripQuery(location.href);
console.log(baseUrl, currentUrl);
if (currentUrl + '/' === baseUrl) {
var newUrl = location.href.replace(currentUrl, baseUrl);
console.log('do redirect to', newUrl);
location.href = newUrl;
}
</script>
<script src="https://code.angularjs.org/1.5.0/angular.js"></script>
<script>
angular.module('app', [])
.config(function($locationProvider) {
$locationProvider.html5Mode(true);
})
.run(function($location) {
console.log('OK', $location.absUrl());
});
</script>
</body>
</html>
I find it strange not to handle this in angular and to rely on the configuration of the web server. But this is your decision, of course. I will try what you suggest. Will it be in the doc for people hitting the same wall?
The reason not to handle it in Angular is that adding the code I suggest could well break a lot of apps unnecessarily for this unusual use case - to put this in context, this is the behaviour since 2011 and you are the first person to report having the difficulty.
I also face this problem , Google bring me here. Hope angular team consider this condition.
I have written $locationProvider.html5Mode(true) this in my config function and set base url with base href="/foldername/" My problem is that when I run my code in browser with ip and folder name i.e localhost/foldername, its working fine. But when i am reloading my page by hitting refresh button of browser, its not working. Could please tell me what is the issue with my code .
@shitala-cuelogic have you configured your webserver to rewrite "deep" links back to the index.html?
@petebacondarwin thanks for your response. Is it mandatory to configured webserver to rewrite "deep" links back to the index.html ? is there is any other ways in which we can solve this problem?
target="_self"
ref: https://docs.angularjs.org/guide/$location#html-link-rewriting
@shitala-cuelogic - if you want to use HTML5 deeplinking then you must indeed configure your server.
That's a pain.
I was looking for the solution over a year ago:
http://stackoverflow.com/questions/34156213/angular-html5mode-prevents-links-outside-of-base-href-to-be-loaded
Had to use a url with two parts, so rather than having /myurl?param=value I had to use /myurl/app?param=value which is worse from seo perspective. I didn't want to have /myurl/?param=value which is just ugly.
Most helpful comment
I find it strange not to handle this in angular and to rely on the configuration of the web server. But this is your decision, of course. I will try what you suggest. Will it be in the doc for people hitting the same wall?