AngularJs HTML5 routing and IE9

Thursday, May 02, 2013

We seem to be pushing the limits of AngularJS, and we've only started using it. The framework is very promising but definitely very new and a little rough in some areas.

We'd like to avoid hash urls in our solutions. The backend can respond to our routes just fine, and our backend framework is able to serve up the correct content (not necessarily the same content) for any area of the application. Using HTML5 mode and the history.pushState api, we can get clean URLs and no 404 errors on refresh...awesome.

Enter, IE9 (top light blue line). Our current browser standards are IE9+, Desktop/Android Chrome, FF, and IOS Safari. All browsers support history.pushState except IE9. The browser has had a recent significant drop in usage, but we can't ignore it quite yet.

At first, we thought we could set $locationProvider.html5Mode(true) and it would either a) break in IE9 calling a function that didn't exist (but we could define as window.location.assign(url)), or b) do a browser-based redirect based on window.location.assign.  Well, RTFM:  what it does is actually relatively painful.  It falls back to hashbang syntax.  This is similar to history.js, and IMHO, is broken in the same way as that library.  Now you have URLs you can't share between browsers without crazy (and slow) workarounds.

I tried to work around the problem by providing a history.pushState implementation.  This fooled Angular into thinking it was compliant, but caused some redirect loops and ultimately didn't work out.  Next, I tried first intercepting routeChangeStart and calling event.preventDefault to no avail, then locationChangeStart also without success (I'm informed this does work, but for some reason I didn't see it, at least not in IE9).

Finally, it hit me...since we're not programmatically changing locations (just using anchor tags), we could simply determine if pushState is available and perform the following functions if it is not:

  1. Avoid defining any routes with the route provider
  2. Take our <script id='whateverroutewearecurrentlyon' type='text/ng-template'/> that is automatically generated on the backend for the current URL and manually add it to the ng-view element.
  3. Remove the ng-view attribute (for completeness) and add an ng-controller attribute pointing to the controller for the current route.
  4. Disable any pre-loading for pages other than the one on which we reside.
This technique can only work in a pretty specific set of circumstances, but I think a properly designed backend system should be able to meet the criteria:
  1. Any URL used by history.pushState actually generates appropriate content from the server.
  2. The server at any URL provides the page layout and the application code.  Providing the current page content in the script tag is nice (that's what we do) but it's not strictly required (#2 above could be done via Ajax).
  3. The application code is light enough that you (and your users) can put up with full page loads in non-compliant browsers until users upgrade.  I wouldn't want to try this technique on gmail. ;-)


10 comments:

Unknown said...

Hi Emil,

How did you deal with Angular routes with html5 enabled in your backend and serve up the correct content on page refresh?

Thanks,

Emil Lerch said...

@Unknown There's a couple ways to handle that. The way most people tend to do it is to serve identical content to any request (you can do server-side URL redirection on most web servers, or, if you're using Asp.NET MVC you can use a greedy MVC route). Angular would then look at the route and grab the view from the server as a separate request on page load.

I'm actually embedding the current view in a script tag so Angular doesn't need to go back to the server for the current view content. This is done by setting the script type to 'text/ng-template' and the id to the name of the route. This blog post explains more: http://www.bennadel.com/blog/2430-Inlining-AngularJS-Templates-Using-ColdFusion.htm This has two advantages - Google can now crawl the site, and I believe it's a better accessibility story (although I haven't tested this technique with a screen reader).

Unknown said...

Thanks Emil for your response. how do you handle in situations when users refresh the page or If users bookmark one of your routes which isn't a real url?

Emil Lerch said...

That's the point of the setup above. With the greedy routes (or url redirection server side), the URLs are all real/recognized by the back end.

Emil Lerch said...

That's the point of the setup above. With the greedy routes (or url redirection server side), the URLs are all real/recognized by the back end.

sss lokesh said...

one my instructor suggested me this site, really excellent
Angularjs online Training

Steve Smith said...

Hi Emil,
Great Work.Very good explanation on AngularJs HTML5 routing using HTML5 mode and the history.pushState api.
AngularJs Training
AngularJs Training in Chennai

Sankar lp said...

Very interesting article.. great examples..

JavaScript Training
JavaScript Training in Chennai
javascript tutorials
Online JavaScript Training

Steve Smith said...

Great and Useful Article.

Java Online Training

Java Course Online

Java EE course

Java Course in Chennai

Java Training in Chennai

Java Training Institutes in Chennai

Java Interview Questions

Java Interview Questions

Steve Smith said...

Great and Useful Article.

Java Online Training

Java Course Online

Java EE course

Java Course in Chennai

Java Training in Chennai

Java Training Institutes in Chennai

Java Interview Questions

Java Interview Questions

Emil's Wicked Cool Blog