angular-ui / ui-router

The de-facto solution to flexible routing with nested views in AngularJS
http://ui-router.github.io/
MIT License
13.53k stars 3k forks source link

html5Mode appears not to work #116

Closed laurelnaiad closed 11 years ago

laurelnaiad commented 11 years ago

(note I've edited this since I first posted it)

I was happy to see from https://github.com/angular-ui/ui-router/issues/69 that html5Mode should just work. It doesn't work for me with the sample in Chrome or Firefox. I haven't tested any other browser.

Steps:

http://localhost:3006/sample

everything works.

change three lines in index.html (starting at line 50)

    [        '$stateProvider', '$routeProvider', '$urlRouterProvider', '$locationProvider',
    function ($stateProvider,   $routeProvider,   $urlRouterProvider, $locationProvider) {
      $locationProvider.html5Mode(true);

After that change, when I go to /sample the content for that page loads and displays properly and then I'm "location.replaced" to "/" and everything ceases to function.

I tried to figure out where things head south but I just don't know the code well enough.

Top level routes and their dynamic segments do work if I copy the build and lib directories into the sample directory and change the paths to them, but deeper routes do not (e.g. /contacts/1 works but /contacts/1/item/a does not).

ksperling commented 11 years ago

Well, the sample app currently uses plain anchors to do most of the navigation. If you're using html5 mode, this means the browser will actually try to load e.g. '/sample/contacts/1' and get a 404 because there is no such file.

You'd have to configure the server to rewrite any URL under /sample/ to /sample/index.html. However this would still cause a full page reload -- you'd also need a click handler on every link to do a preventDefault() and call $state.transitionTo() to perform the transition instead of letting the browser update the location itself. Doing this automatically is something we should look at in relation to the planned directive for generating hrefs.

laurelnaiad commented 11 years ago

The problem isn't URL rewriting nor is it click handling...URL rewriting is fine with me, and of course necessary for HTML5 mode. angular core takes care of the rest (unless for some reason the custom view needs also to do so).

The main/big problem is that when I load /sample it redirects to transitions to /. Or if the root of my server is mapped to a higher level directory, then /xyz/123/sample redirects to transitions to / ...

ui-router is confused by the lack of # in the URL -- essentially, it seems not to "know where the base URL of the app is" and tramples its way up the path to the root of the server.

I'm confused about how your answer relates to the other issue where you stated that HTML5 mode should "just work". It now sounds like you're saying that a bunch of work on ui-router for hrefs and clicks is necessary before it will work... did new information come to light? The hrefs seem to work properly and the browser is not reloading pages from the server to the best of my knowledge, so I think that if ui-rotuer respects the app root then all will be well.

laurelnaiad commented 11 years ago

P.S. If my comments aren't clear, one way to see the issue for yourself is to update those three lines of code... I'm assuming this behavior isn't desired and that the fix would hopefully be easier than all of the nasty stuff you mentioned in your comment :) -- I just can't understand what all is going on with the "rules" array in the code enough to know why it is choosing to transition to "/".

ggoodman commented 11 years ago

I've had this same issue. To resolve it, I had to do this:


app.config ["$locationProvider", "$routeProvider", ($locationProvider, $routeProvider) ->
  $locationProvider.html5Mode(true)
  $routeProvider.when "/",
    controller: ["$state", ($state) -> 
      $state.transitionTo("catalogue.popular")
    ]
]

Basically, it seems that in html5Mode, ui-router will fail to identify the route at bootstrap-time and the starting route needs to be bootstrapped using $routeProvider.

I don't know if this is a bug or feature though!

laurelnaiad commented 11 years ago

It certainly looks like a bug to me. It seems like your solution will work around the initial bounce to the root of the server, but does it fix any deeper states? Does it allow deep linking?

I'm compelled to say, as a potential adopter, that I'm a little scared that the team seems not to have noticed this behavior. I'm not trying to be picky and I know this is prerelease, but is html5 history support on the roadmap? It would seem that if this issue lingers it could fester in the codebase, since it's a routing problem and this is all about routing. I wish I could understand the method of constructing the rules array. I don't know how many assumptions about the presence of a pound sign are in there.

jeme commented 11 years ago

If you switch to html mode, you will need quite some restructuring.... one example is that when you have the sample, you have the path http://localhost:8000/sample as root (can't recall if the port is correct)... that should load fine, but then it redirects the path to: http://localhost:8000/ and then nothing works because it's looking for templates etc in http://localhost:8000/ not http://localhost:8000/sample as under html5 mode we are relative to domain, not file...

another way is ofc. (properly) to switch all routes to comply with the "sample" subfolder, so / becomes /sample, /contacts becomes /sample/contacts and all templates should also have that prep-ended. so contacts.html to samples/contacts.html

At least in my own angular-router, starting the dev-server (grunt-contrib-connect) in the sample app I wanted to switch to html5mode, then moving all sources that was located outside that path into a local path worked for it... But as I said, quite some restructuring.

laurelnaiad commented 11 years ago

Thanks, @jeme, I'm not entirely sure whether you're indicating that the sample would need restructuring or the router would need restructuring.

I'll write a test case and post back with a link to my branch. If the team wants to fold in the test case, great.

I really hope html5 mode support can be worked in (assuming that we're talking about something other than just the sample not doing the right thing).

laurelnaiad commented 11 years ago

Note: I don't think the correct behavior is to interpret paths relative to the server root in HTML5 mode... or is it?

jeme commented 11 years ago

@stu-salsbury I haven't tried it with ui-router, but in angular-routing's case, it was purely the sample that was broken for html5 mode...

And yes, at least for routes it is the correct behavior to see the path relative to the domain, as you can't see the difference as there is no # to separate you from the "routing route" and the "actual route".

So instead of have the following stack of locations:

http://angular-ui.github.io/ui-router/sample/#/
http://angular-ui.github.io/ui-router/sample/#/contacts
http://angular-ui.github.io/ui-router/sample/#/contacts/1
http://angular-ui.github.io/ui-router/sample/#/about

you would have:

http://angular-ui.github.io/ui-router/sample/
http://angular-ui.github.io/ui-router/sample/contacts
http://angular-ui.github.io/ui-router/sample/contacts/1
http://angular-ui.github.io/ui-router/sample/about

And how will you know that you need to work relative to: http://angular-ui.github.io/ui-router/sample/ You can't do some clever "I loaded here first", because when you then resort to loading http://angular-ui.github.io/ui-router/sample/contacts/1 that would be a new definition of all paths... Which brings the next point...

All possible paths must return the same root index.html from the server, no matter the path... this is what @ksperling is talking about... not sure if the simple web-server in grunt-contrib-connect is capable of that...

But as you see, html5mode brings allot of things to think about from an application standpoint, all routes needs to be accepted by the server as well as the client, the server can obviously in all cases serve the same file, but it still has to detect that... This also means you have to think about actual server calls, like serving templates, REST service calls and so forth...

You would properly go ahead at set your server up to do something like:

when '/api/...' serve json data // API Routes for REST interfaces.
when '/tpl/...' serve template.html // Template Routes for serving the partials
when '*' serve index.html //everything else just posts back the main page.

(and important that it's in that order, so the last one doesn't hijack the others)

You may also wan't to read about the $location service and html5 mode here: http://docs.angularjs.org/guide/dev_guide.services.$location

laurelnaiad commented 11 years ago

I understand URL rewriting and the necessity of it for deep linking in html5 history mode. I think we can put that part aside.

Maybe I should work on proving to myself that html5 history mode does indeed work the way I think it does in the core router. Perhaps I imagined it.

jeme commented 11 years ago

@stu-salsbury you could use url-rewritting as well, but the above was actually server side routing, which is the concept I use in ASP.NET to serve pages...

But ui-router (as well as angular-routing) is just using $location.path() to match against, just like the original router does:

So the rest is up to the $location service and it's implementation. So I doubt we can do anything different. But if you find something I would absolutely like to know so I can have a look.

If you use server side routing, it can't even tell where the index.html really came from, it would just think that all those resources happened to serve the exact same file, so it would look like the file was copied out all over the server from the browsers point of view.

laurelnaiad commented 11 years ago

Ok, tested the default router again. It does indeed require that routes be prefixed with the path to the app root I think I must have been remembering ember behavior, which if true, ember must be determining the app path based on the location of index.html. I guess that's not a feature of angular, so my bad for that. I'll focus my tests without that assumption. Thank you for pointing that out.

jeme commented 11 years ago

which if if true must be determining the app path based on the location of index.html.

@stu-salsbury I would consider that a just as error prone approach, because as I mentioned above, if you use server side routing to construct your response, the client wouldn't be the wiser to where index.html existed... It would think it existed in all the paths that returned it... (Index.html might actually not even exist in a server path at all, could be stored in a database or something completely different)... So server side routing seems like it would break Ember on that if that is the case.

But that is a completely different topic, and as interesting that may be, it's off-topic...

Let me know if you still have trouble with figuring html5mode out.

laurelnaiad commented 11 years ago

I put so much FUD into this, I'm going to close the issue. I'm sorry for the noise. I'll open a new one with a test case implemented if there is still some demonstrable problem. The issue of how one could detect the app path under a URL-rewriting scenario seems basically to be moot unless the ui-router wants to break from the core's behavior, which may not be wise.

EDIT: I think I just figured out the problem originally brought me into the quagmire of of this non-issue. It seems that maybe the paths to the templates are relative to the route, not to the app path nor the server root. Now that I've got that figured out, html5 mode works for me.

ggoodman commented 11 years ago

I think both of you are missing the easy solution to this issue which is the <base href=""> tag in html5. This tag will cause all relative urls to be matched as if they were subordinate to the url specified.

Here is something I've been working on that uses this approach: http://embed.plnkr.co/Qv0uCROy1jbAmhjQ2TkR/preview (Note, it may do 'nothing' if my dev server is offline at the time you check, but the code is all there).

ksperling commented 11 years ago

I don't have the references at hand at the moment, but there was a fair bit of discussion on some angular lists and issues around base href having unintended side effects, e.g. anchor-only hrefs become relative to the base href as well. It seems what's need is a way to set a base href purely for $location.

morgs32 commented 10 years ago

Also running into this problem and haven't found an answer. If I'm always providing the index.html page no matter the link to a nested state, then it's always looking for url additions to that link (of which there are always none)... can we see an example of this working?

s10mcow commented 10 years ago

My fix for this - spending a lot of time to debug and stumbling across this when comparing my code to the sample app - is this:

in the config function of your angular app:

$locationProvider.html5Mode(true).hashPrefix('!');

Then when I try to reload the page I get nothing... I then add this to my html header:

<base href="/">

Bam, I get pages that reload correctly and clean URLs.

Beer time... 
connor11528 commented 10 years ago

whoa don't know how this works (prefix '!'?) but beer:thirty agreed

hardywu commented 10 years ago
<base href="/">

not working in angular 1.2.20

kpgarrod commented 10 years ago

This doesn't seem to work in Angular 1.3.0 either

talhamaniar commented 9 years ago

while implementing "$locationProvider.html5Mode(true).hashPrefix('!');". When i refresh the page eg: url: localhost:8000 /project. i am getting issue in page " NOT FOUND ". Any one can help me where i am wrong..

vb8190 commented 9 years ago

I am facing the same issue. I am using angular-ui router 0.27 version with angularJS 1.2.21 version. When I turn on html5 mode to true and use and try to reload the page, I get following error

"Unable to load the webpage because the server sent no data."

Error code: ERR_EMPTY_RESPONSE.

I am trying to get bookmarking working and for that I had to go away from hashes in the URL so that the server receives the complete url.

Please re-open this defect.

christopherthielen commented 9 years ago

As far as I can tell, html5 mode works as advertised. Show me what bug there is.

pengzewen commented 9 years ago

If you try to use urlRouteProvider with html5Mode, you might reproduce the problem.

christopherthielen commented 9 years ago

@pengzewen show me.

pengzewen commented 9 years ago

turns out it's the issue of apache configuration. you are right, it works as the description in documentation. Sent from my iPhone

On Nov 18, 2014, at 3:38 PM, Chris Thielen notifications@github.com wrote:

@pengzewen show me.

— Reply to this email directly or view it on GitHub.

abhishekbhalani commented 7 years ago

@christopherthielen I am getting same issue.. When we manually enter on browser after HTML5 Mode then it page not found error. otherwise it working..

anil826 commented 7 years ago

Finally I got a way to to solve this issue by server side as it's more like an issue with AngularJs itself I am using 1.5 Angularjs and I got same issue on reload the page. But after adding below code in my server.js file it is save my day but it's not a proper solution or not a good way .

app.use(function(req, res, next){
  var d = res.status(404);
     if(d){
        res.sendfile('index.html');
     }
});