Angular.js: AngularJS Hash Bang Urls and Query Parameter ordering

Created on 7 Feb 2014  路  18Comments  路  Source: angular/angular.js

According to AngularJS documentation, it states that a hash bang URL is in this format:

http://foo.com/#!/bar?baz=23

This indicates that the hash fragment is between the path and the query parameters of the URL. It's DOMAIN - HASH FRAGMENT - QUERY PARAMETER

However on Google AJAX Crawling Specification, when it converts a hash bang URL into the _escaped_fragment_ format for the purposes of indexing and then converts it back for search results, it instead places the hash fragment after the query parameter. (See the Search Results section)

http://foo.com/?baz=23#!/bar

This indicates DOMAIN - QUERY PARAMETER - HASH FRAGMENT.

It seems that both are a valid URL structure (try it on a browser with any non SPA application). However if you are using this URL format on any AngularJS site, the site correctly ascertains the path, however it "forgets" the query parameters.

So if you go to http://docs.angularjs.org?key=value#!/guide, it turns into http://docs.angularjs.org/guide. As you can see it forgets the query parameters. If I'm using the Google AJAX specification, then this would result in search results having URLs that don't work properly as their query parameters gets dropped.

I did some testing with both urls which has illuminated the problem:

Case 1: Using DOMAIN - HASH - QUERY PARAMETER (AngularJS format) (http://example.com/#!/path?key=value), the query parameters never gets received by the server, but it is remembered on the client side. It's also available in $location.search().

Case 2: Using DOMAIN - QUERY PARAMETER - HASH (Google's format) (http://example.com/?key=value#!/path), the query parameter gets received by the server (as it is not after the #), but it is forgotten on the client side. It's also not available in $location.search().

Perhaps a solution can be made for case 2. So that when there is query parameter and then hash fragment, AngularJS can correctly remember the query parameter and shift it to the end of the URL (if converting to HTML5 urls) and also make it accessible with $location.search(). Case 1 cannot be solved due to the URL standards as everything after the # is not sent to the server.

$location moderate investigation bug

Most helpful comment

My problem is that IE10 auto-rewrites "https://host/#hash?search=..." to "https://host/?hash&search=..." so our Angular2 routing is all broken.

All 18 comments

Angular does not remove the query before the hash bang. For angularjs.org this is done on the server with a redirect. See this jsfiddle: http://jsfiddle.net/dVNPz/show/?a=b#!/test?c=d

However you are right in that Angular does not include the query parameter before the hash into $location when in hashbang mode. The reason for this is that Angular can't change anything before the hash without the browser reloading the page.

How did you get the value for the _escaped_fragment_?

With regards to the value of _escaped_fragment_, that's just based off the Google AJAX Crawling Specification.

So the server is the one that removes the query before the hash bang? If one could make the server keep the query, can AngularJS extract those values and place it in $location? It doesn't need to change the values before the hash to read the values before the hash right?

Right, the server behind angularjs.org does remove the query before the hash bang. Your server might not, depending on how you configured it.

Angular could provide this extra information, but the other parts of the url before the # are not present on $location either (e.g. the path). The best thing would be to have a new service that encapsulates window.location only for reading purposes.

For your app, you can just do this (e.g. directly access window.location) and read out the query value.

Before we work on this, we need verify that Google really moves the query part from behind the hash to the front of it, and does not escape it as well.

The part where it mentions it in the Google AJAX specification is here:

Search result

The search engine agrees to display in the search results the corresponding pretty URLs:

    domain[:port]/path#!hashfragment
    domain[:port]/path?queryparams#!hashfragment
    domain[:port]/path
    domain[:port]/path?queryparams 

Specifically:

    domain[:port]/path?queryparams#!hashfragment

Now Google will put everything after the hash fragment into the _escaped_fragment_. This will result in an AngularJS compatible URL. This is not the problem.

The problem is that DOMAIN HASH QS and DOMAIN QS HASH are both valid URL structures. And while we cant fix DOMAIN HASH QS, surely AngularJS should be able to extract the QS from DOMAIN QS HASH.

Query parameters should come first according to the URI syntax spec: http://tools.ietf.org/html/std66#section-3

Otherwise they are just part of the fragment, I believe. Angular is free to do what it wants within the fragment, but I also would like Angular to read the query parameters when the URI is in the form of DOMAIN - QUERY PARAMETER - HASH FRAGMENT

Any updates on this issue. Any idea whether the DOMAIN QS HASH will be supported by Angular? I.e:$location.search() returning the values of the query string in the case of DOMAIN QS HASH.

Thank you

Would it be possible to change the routing to support DOMAIN QS HASH when NOT in hashbang mode? Here's the situation my team is in:

  1. We used to be in hashbang mode.
  2. Various 3rd parties are still sending us traffic to the hashbang URLs with DOMAIN QS HASH structure. Most of the QSs are the standard google analytics campaign params that still seemed to "work" in that the correct data was sent to google analytics.
  3. We switched to html5 mode, and now the routing is broken for all of those links. Example that used to work before the switch : https://www.delivery.com/?key=value#!%2Fcities%2Fnyc%2Fcategories%2Frestaurant%2Fokami

This is also breaking google analytics campaign tracking when linking to hashbang URLs.

Let's take a URL like domain.com/#!/path?utm_campaign=blah. If you go to this URL and print out window.location.search, there is nothing there.

I'm assuming that google analytics is going to use whatever it finds in window.location.search for its campaign tracking.

If you use the DOMAIN QS HASH version like domain.com?utm_campaign=blah#!/path, the query string is in window.location.search. The problem is that DQS breaks the routing when in html5 mode.

Is there any update on this? I am having the same problem as what "timomall" has reported on 9 Dec 2014.

There are too different concepts here:

  1. On one hand we have the _query_ part of the URL (that comes before the _hash fragment_). This is also visible to the server (i.e. it is part of a request) and is not used by Angular for it's client-side routing (in the default hashbang mode).
  2. On the other hand, the hash fragment (which is used by Angular for it's client-side routing) may contain a "query-like" part which typically is part of the _hash fragment_ (and is ignored by the server). This is the part that Angular uses (and makes available under $location.search()).
    As far as the browser is concerned, though, this is just part of the _hash fragment_ and totally different (and independent) from the actual _query_ part of the URL (if any).

In order to avoid confusing crawlers, one should probably encode that second "query-like" part of the _hash fragment_, when using an _escaped_fragment_ URL.

In any case, the Google AJAX Crawling Specification has been deprecated since October 2015.
Closing as I don't believe there is anything we can fix on the Angular side of things.

My problem is not with the crawler at all. My code was using the non-HTML5 mode and we have URLs given to external customers which are of the form https://abcd.efgh?utm_campaign=abcd#/abcd/12. Now, if I turn on HTML5 mode and the external customer tries to use the URL already with them, the URL changes in the browser to just https://abcd.efgh/abcd/12. The query parameter of utm_campaign is completely lost.

How can I get angularjs to preserve the query parameter so that the URL becomes https://abcd.efgh/abcd/12?utm_campaign=abcd when somebody tries to use the old URL?

@nmandya, I suspect it is a server configuration issue. But you can't really mix URL styles (hashbang and html5 mode) and have things work as expected, I'm afraid.

In https://abcd.efgh?utm_campaign=abcd#/abcd/12, Angular is only concerned with the part after #, namely /abcd/12. It sees it as:

$location.path()   === '/abcd/12'
$location.search() === {}
$location.hash()   === ''

The equivalent of the above in html5 mode is indeed https://abcd.efgh/abcd/12.

The "problem" is that in hashbang mode, there is the _query_ part of the URL (before the #), which Angular is not concerned with and the _query_ part of the client-side routing (after the #) which Angular "sees" and interacts with. In html5 mode there is only one _query_ path.
So you can't really use the hashbang mode URLs in html5 mode (unless you do some server-side transformation maybe, converting ?utm_campaign=abcd#/abcd/12 to #/abcd/12?utm_campaign=abcd).

My problem is that IE10 auto-rewrites "https://host/#hash?search=..." to "https://host/?hash&search=..." so our Angular2 routing is all broken.

If it's a Angular2 issue, you should raise it on https://github.com/angular/angular.
Although it sounds more like a IE10 issue...

Hi there, hate to comment on a closed, old issue, but the fact is that the URI RFC (RFC3986) is pretty clear that the fragment identifier comes _after_ the query string. The way the non-html5 mode of Angular's location service works is to lose the query portion of the URL if it appears prior to the fragment. I have a small patch to the location service that fixes this issue. I'm in the process of submitting a pull request for it. Hopefully it will help someone.

Why AngularJS supports hashbang ? Can it be solved in future ? It just does not work with server side php ,especially post methods .

For support questions, please, use one of the appropriate support channels.
Thx!

Was this page helpful?
0 / 5 - 0 ratings