1

Reloading the page or manually type URL like "localhost/mysite.com/contact" gives wrong "Not Found"(404 error) on the server. This is because MVC does not know about angular routing and by MVC pattern it will look for an MVC page for 'contact' which does not exist in MVC routing path. I think there is a solution by .haccess file by redirect all links to the index page, but it, not a good idea.

I already add a baseURL constant to the application in head. and the js part is below:

 app.config(function($routeProvider,$locationProvider) {
    $routeProvider.
    when("/contact", {
        templateUrl : "partial/contact.html"
    }).
    when("/aboutus", {
        templateUrl : "aboutus.html"
    }).
    when("/service", {
        templateUrl : "service.html"
    }).
    otherwise({
      redrictTo: "index.html"
    })

     $locationProvider.html5Mode(true);
});

How can I fix the issue angular to get the correct page (localhost/mysite.com/contact) when I go to the contact page by typing URL manually.

Sanjib Debnath
  • 3,556
  • 2
  • 22
  • 16
  • You can follow this link. http://stackoverflow.com/questions/10865480/mod-rewrite-to-index-html-breaks-relative-paths-for-deep-urls – Piyush.kapoor Jun 23 '16 at 10:30
  • "I think there is a solution by .haccess file by redirect all links to the index page, but it, not a good idea." - Why do you think it is a bad idea? I've seen this solution in production and it works just fine. – Merlyn Morgan-Graham Jun 23 '16 at 10:32
  • @MerlynMorgan-Graham — What's the point of having different URLs that the server can recognise if you're going to serve up the same content for all of them? What happens when the JavaScript fails, since there's no way to fallback to the right content? – Quentin Jun 23 '16 at 10:35
  • @Quentin Both of these trade-offs are good points. "What's the point of having different URLs" - you don't actually have to serve index.html off *all* subpaths, just the ones that correspond to client-side URLs. "What happens when the JavaScript fails" - [many people choose to not care about this case these days since this is becoming more infrequent over time](http://stackoverflow.com/questions/9478737/browser-statistics-on-javascript-disabled). – Merlyn Morgan-Graham Jun 23 '16 at 10:43
  • There's also been a recent additional push for server-side MVC. If you're using a client-side MVC library rather than completely building it from scratch yourself, then making the server return the pre-rendered page may be an option for you, per Quentin's suggestion. – Merlyn Morgan-Graham Jun 23 '16 at 10:46
  • @MerlynMorgan-Graham — JavaScript fails for reasons other than "The user turned it off". – Quentin Jun 23 '16 at 10:50
  • @MerlynMorgan-Graham .htaccess not support in all server it is only support in apache server this not a good solution. – Sanjib Debnath Jun 23 '16 at 11:02
  • @Sanjib Debnath well, which production web server are you using? All production servers I know of are capable of doing this type of routing, because this technique is really popular. There are trade-offs, of course, as Quentin is pointing out, but I didn't know if you were aware of them. – Merlyn Morgan-Graham Jun 23 '16 at 11:03
  • @SanjibDebnath — "Using .htaccess" is often (terrible) code for "Changing the server configuration via whatever methods my server supports", which it is in this case. "by redirect all links to the index page" is still a terrible solution to the problem though. – Quentin Jun 23 '16 at 11:05
  • @Quentin trade offs should be made on a cost/benefit analysis rather than on concepts of moral hygiene. Maybe 0% of users of the OP's site will be affected by the choice? Maybe 100%? I've made an answer with the opposing viewpoint to allow more debate. – Merlyn Morgan-Graham Jun 23 '16 at 11:15
  • @MerlynMorgan-Graham — "Moral hygiene"? Everything I've said has been grounded in practicalities. There are *only* costs to using html5mode + internal redirect. You have to have an extra line of client side code, you have to configure your server, and you break caching (since you have duplicate content on every URL and each one has to be cached separately). If you aren't going to render the pages server side, then don't use html5mode in the first place. – Quentin Jun 23 '16 at 11:22
  • @Quentin again, many people make a different decision than you are advocating. You could also solve it using the fragment identifier, as you pointed out. Either way definitely throws out HTTP caching of the client code. This doesn't matter on some sites, as they serve the bulky parts of the client via CDN and edge-servers anyhow, and/or use immutable scripts and do permanent caching. Those sites rely on AJAX to get any non-template content. HTTP caching works fine with that approach. – Merlyn Morgan-Graham Jun 23 '16 at 11:27
  • Using the fragment identifier doesn't throw out HTTP caching, and I wasn't talking about the caching of dependent resources. There are still zero benefits to using html5mode + internal redirect. Code / Benefit = Divide by zero error. – Quentin Jun 23 '16 at 11:28
  • "many people make a different decision than you are advocating" — they do, but nobody (here at least) has been able to justify that decision. People make terrible, uninformed decisions every day. – Quentin Jun 23 '16 at 11:29
  • @Quentin "Using the fragment identifier doesn't throw out HTTP caching" this statement is incorrect. Or at least, it is no more correct than "Using html5mode + internal redirect doesn't throw out HTTP caching". – Merlyn Morgan-Graham Jun 23 '16 at 11:33
  • @MerlynMorgan-Graham — Wrong. `http://example.com/foo` and `http://example.com/bar` are different URLs and will be cached independently (and must be requested independently). `http://example.com/#foo` and `http://example.com/#bar` differ only in the fragment, which is processed client side, so `http://example.com` will be cached and used for both requests. – Quentin Jun 23 '16 at 11:35
  • @Quentin True. For some reason I thought you were implying that `/#foo` could somehow be cached independently of `/#bar`. I think your point is valid. There is a use case of brownfield development that using html5mode would support. The OP doesn't look like they're doing that, but it may be important for some people in the future. I've updated my answer to address points brought up in our conversation. – Merlyn Morgan-Graham Jun 23 '16 at 12:19

2 Answers2

2

Angular routing, in HTML 5 mode, uses the history API under the hood.

The history API is designed with the intention that you:

  1. Load a page
  2. Run JavaScript to change the content of the page in such a way that it becomes a different page
  3. Use pushState to change the URL in the browser so it is the URL of that page

The idea being that using JavaScript for step 2 should be faster (or better in some other way) than loading the whole page from the server.

This is because MVC does not know about angular routing and by MVC pattern it will look for an MVC page for 'contact' which does not exist in MVC routing path

This is your problem. The history API is designed so that your server side code should know about that.

If a user went directly to the second URL, then the server should be able to present them with that page directly. i.e. it shouldn't throw a 404 and it shouldn't load the homepage and then transform it with JavaScript.

JavaScript is unreliable. Follow the principles of Progressive Enhancement and Unobtrusive JavaScript

tl;dr: You need to write server side code to mirror your client side code.

If you don't want to use URLs that the server can recognise for each page, then don't use $locationProvider.html5Mode(true); and go back to the bad old days of hashbangs.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • 1
    This is not the one true wisdom on the subject. Many people happily make a different trade off than you're advocating here and rely on client-side MVC routing without using the fragment identifier. I am not equipped to actually debate this point, nor do I care that much, but I wanted to point out that there is more than one side to this story. – Merlyn Morgan-Graham Jun 23 '16 at 10:38
  • @MerlynMorgan-Graham — If you're not going to use the server to provide the right content for each URL, then there is no point in using html5Mode. The only benefit it has is that it lets you have a server side fallback. – Quentin Jun 23 '16 at 10:51
  • Maybe the problem is that I don't understand Angular's server-side rendering support. I read the problem generically, didn't see an angular tag, and assumed he was asking a generalized question. There are (or at least were) frameworks that required a lot of effort to allow the server to actually render pages before sending them to the client. For those cases, a lot of people have chosen to spend their time on different things than enabling server-side routing to return the resulting page. They choose to let the client care about the URL, and the server to return index.html for all routes – Merlyn Morgan-Graham Jun 23 '16 at 10:55
  • There are many website using this method goto the [link](angularjs.org) the website using this method. – Sanjib Debnath Jun 23 '16 at 10:57
  • 1
    @MerlynMorgan-Graham — The OP appears to be using some other server side MVC framework (since they are just calling it MVC, I'm guessing it is ASP.NET MVC) which has no particular connection to Angular. Using that simply requires duplicating the logic on the server side. Which *is* a lot of effort. Building a robust SPA is a lot of effort. – Quentin Jun 23 '16 at 10:58
0

Quentin's solution of doing the rendering server-side is absolutely correct, if you can afford to get server-side rendering to work. If you can't get it to work, or it turns out to be too costly to get it working, then you can go with a couple solutions.

Disable HTML 5 mode URL handling

 // Remove/change this line - $locationProvider.html5Mode(true);

Quentin has already suggested this solution. He warns strongly against it, and there are some drawbacks. However, it still might be the correct trade off for you. See below.

Use server-side redirects so your URL routing is only handled on the client side

You mentioned this in your question:

I think there is a solution by .haccess file by redirect all links to the index page ...

Most production routers are capable of doing this type of routing. This doesn't preclude you from routing other apps on the same server, if you are careful to keep the URLs for each app from clobbering each other.

Generally you should prefer the other solution of turning off HTML 5 routes, and going with fragment identifier URLs for the client. However it could make sense to use this option when you're doing brownfield development and have to mimic an existing URL structure by using HTML 5 mode URLs.

Trading off cost vs effort

You said you heard it was "not a good idea" to hack the .htaccess to always return the client code. Generally speaking, this is correct. This will rely on the client-side MVC framework to do routing, and will partially waste the ability of the server to do routing. Fully leveraging HTTP caching will also probably require additional work with this approach, and won't ever be quite perfect.

As Quentin pointed out, there are also robustness trade-offs you are making if you go with this solution. JavaScript breaks if you are not very careful, and can leave your app broken. Some people choose to disable JavaScript. Some sites also aren't capable of scraping a web page that is only rendered on the client side.

The issues of having no JavaScript on the client are decreasing in frequency over time, but you may find that having a less robust site may be a painful thorn for your user base and cause you a significant loss in users. If so, you may be justified in cost to get server-side routing to work reliably and do progressive enhancement.

If you don't have a lot of users with JavaScript disabled, then it might be worth it to you to hack the solution (html5Mode == false or .htaccess hacks), and focus on other things like building out features that will make you more revenue or retain users on your site for longer.

Community
  • 1
  • 1
Merlyn Morgan-Graham
  • 58,163
  • 16
  • 128
  • 183