46

Note: This question could also read:

How to support bookmarking of hashbang-less client side MVC frameworks in Java.

I am transitioning an angular app that uses hashtags to one that is html5mode. I have successfully set

$locationProvider.html5Mode(true);

And all links from the landing page (index.html) work fine.

The problem is, if partial URLs are referenced directly, I get a 404, naturally, since the server endpoint definitions aren't coupled to client side-defined routes.

So without HTML5 we get non-SEO friendly hashbangs, but with it we cannot bookmark anything other than the landing page (the page that bootstraps angular).

Why it works if requesting default landing page (index.html) first, ie htpp://mydomain.example/:

  1. Browser requests index.html from server
  2. Server returns index.html, and browser loads angular framework
  3. URL changes are sent to the client side router and the proper partial/s are loaded.

Why it doesn't work if (ie) http://mydomain.example/foo is requested directly from the browser:

  1. Browser requests mydomain/foo from server.
  2. Resource doesn't exist
  3. Server returns 404

Something is missing from this story, I just don't know what. Here are the only two answers I can see...

  • It's by design. Is this how it is supposed to work? That users must always land on the client MVC framework's bootstrap page (usually index.html), and then navigate from there. This is not ideal because state cannot be saved and there is no way to bookmark... not to mention crawling.
  • Server solution. Is this worked around with a server side trick? For example, on all requests, return index.html and immediately call router with additional context. If so, this is against the objective that AngularJS is completely client-side and seems like a hack.
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Robert Christian
  • 18,218
  • 20
  • 74
  • 89
  • You should specify the url base in the head of your main html file () – dimirc Jul 15 '13 at 05:26
  • 2
    The last bullet point is the solution. Implement some kind of filter on the server-side that serves index.html for all the application URLs. Nothing more is needed. The router will automatically display the page associated with the URL in the location bar. – JB Nizet Jul 15 '13 at 20:53
  • Thanks JB. This is pretty ideal, but I am scratching my head as to why I haven't seen this written about anywhere. At a high level, the question is "how do I remove the hashbang from client mvc urls and still provide bookmarkability?" ... seems like it would come up often given how averse the public seems to be about hashbangs in public urls. – Robert Christian Jul 15 '13 at 21:32
  • 9
    Seems like that should be included in the AngularJS documentation. Something like "oh yeah, and when you turn on html5mode you *also* need to make a server side change to make sure that all requests containing your base uri return the bootstrapping html (ie index.html) resource." – Robert Christian Jul 15 '13 at 21:34
  • 1
    I agree with you. The problem is that angular is a pure client-side framework, and that the way to to the server-side configuration depends on the technology used at server-side, which angular doesn't care about. My guess is that they found it obvious enough not to be mentioned. Given the complexity of angular, I would expect it to be used by experimented developers knowing what must be done at server-side. – JB Nizet Jul 15 '13 at 21:48
  • Not directly answering your question, but "without HTML5 we get non-SEO friendly hashbang" need not be true. See http://www.yearofmoo.com/2012/11/angularjs-and-seo.html for how to do SEO with the hashbang urls. Of course that also needs server-side work just like the HTML5 urls. – Duncan Jul 16 '13 at 09:11
  • 1
    I found what I believe is the best solution, and as JB says, it's my second bullet above. It turns out that Angular does mention the server side support requirement in their docs: "Using (html5) mode requires URL rewriting on server side, basically you have to rewrite all your links to entry point of your application (e.g. index.html)" – Robert Christian Jul 18 '13 at 22:36

3 Answers3

40

The AngularJS documentation does in fact mention this

Server side Using this mode requires URL rewriting on server side, basically you have to rewrite all your links to entry point of your application (e.g. index.html)

In this case, one Java-based solution is to tell the server "map all urls to index.html." This can be done in any HTTP Server or container. I implemented this using Java/Servet since I want my application to be HTTP server agnostic (ie Apache versus NginX, or Tomcat/JBoss only).

In web.xml:

  <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <servlet>
      <servlet-name>StaticServlet</servlet-name>
      <jsp-file>/index.jsp</jsp-file>
  </servlet>

  <servlet-mapping>
      <servlet-name>StaticServlet</servlet-name>
      <url-pattern>/app</url-pattern>
  </servlet-mapping>

And index.jsp simply looks like:

<%@ include file="index.html" %>

And add the following to the tag within index.html:

<base href="/app" />
Chic
  • 9,836
  • 4
  • 33
  • 62
Robert Christian
  • 18,218
  • 20
  • 74
  • 89
  • It is not working for me.. Give some more explanation. or give me the file sample if possible. – vimal prakash Oct 31 '15 at 00:08
  • 1
    Does not work neither for me. In fact, all requests are forwarded to `index.jsp`, including the ones for `.js` or `.css` resources, which is not correct. – Romain Linsolas Apr 19 '16 at 12:21
  • Basically you have to register a servlet-mapping to the static servlet for each entry point of your application. So in the example above the application has the entry point /app. If you have more entry points just add another servlet-mapping for each of them or use /* which covered all entry points in my case. – Tobias Michelchen Jun 23 '16 at 13:15
  • 1
    thanks. it worked for me. i just need to add my context root as it was only showing #. Now it has /contextroot#/... So when user refresh the browser, it does not break anymore. – arn-arn Oct 18 '16 at 19:34
3

I spent some time thinking about this for my PHP site. I hang all my server side code off the /api route to keep it separate from Angular. Here is the solution I came up with by updating my apache config:

RewriteEngine on
#let the php framework do its thing
RewriteRule ^(api/.*)$ index.php?url=$1 [QSA,L,NC]
#let angular do its thing
RewriteCond %{REQUEST_FILENAME} !-f      
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) index.html [NC,L]

I wrote up how I went about this using the Flight PHP framework at http://www.awnage.com/2013/10/10/taking-flight-with-angularjs/

Awnage
  • 94
  • 5
  • I am new to java world. I dont know how to add the .htaccess file or apache config. can you please add that also in the tutorial. like which folder and which file. ect... – vimal prakash Oct 31 '15 at 00:09
  • 1
    .htaccess and Apache config are related to Apache http server, not Java. These have been around for decades and are well documented. – Robert Christian Mar 28 '16 at 22:06
-3

The URLs that you link to, and place in the user's address bar, should refer to valid content on the server, and if at all possible, they should refer to the correct content. Sure, you can just serve the same page for every URL, and have the client code go off and load the real content, and things function pretty much the way they do in the hash-URL world. In fact, that's the least-code way to go. But it also qualifies as "breaking the internet", which is why HTML5 and History API give you a way to link to semantically correct URLs.

As a small example, If you go to https://github.com/kriskowal/q and you click on "examples", the client-side code will load the "examples" direcory into the file browser without leaving the page, and the URL bar will read https://github.com/kriskowal/q/tree/master/examples. If you go to https://github.com/kriskowal/q/tree/master/examples directly, you get the content of the "examples" directory sent directly to your browser, without client-side code intermediating. Yes, this is harder to do, and maybe impossible in a "single page app", but it's the right thing to do any time it's possible.

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • 2
    Understand this religious argument, but it's basically saying "don't do single page apps." or "If you create single page apps, make sure there is a server side MVC implementation as well so that you don't break the Internet." – Robert Christian Jul 16 '13 at 00:04
  • @rob I'm not gonna lie, single-page apps are a form of brain-damage. In the not-too-distant future we'll see them the same way we see Flash-powered sites today. – hobbs Jul 16 '13 at 02:20
  • 11
    Well now we are getting philosophical and I see your point. But they are inherently different from flash (and applets) off the bat because they aren't proprietary. If web app development started with client Mvc you would probably be averse to coupling the view to the server... Because after all shouldn't the server be a simple rest API if we are to "avoid breaking the Internet?" In my view what's done on the browser has nothing to do with the Internet... The Internet is servers not clients. – Robert Christian Jul 16 '13 at 04:03