Vinz and Tonz
Vinz and Tonz

Reputation: 3587

Angular 2.0 router not working on reloading the browser

I am using Angular 2.0.0-alpha.30 version. When redirect to a different route, then refresh the browser , its showing Cannot GET /route.

Can you help me with figuring why this error happened.

Upvotes: 205

Views: 123366

Answers (30)

Nishan
Nishan

Reputation: 4441

In my case I was using Angular project template with ASP.NET Core 7. After deploying to IIS (Web App on Azure), page refreshes started returning 404 error.

In Program.cs this is already handled with this line of code.

app.MapFallbackToFile("index.html");

But if your index.html is inside a subfolder within wwwroot, this is how to configure it. (This was our case, therefore above line of code didn't really do anything.)

var fileProvider = new PhysicalFileProvider(
    Path.Combine(app.Environment.WebRootPath, "MySubFolder"));
app.MapFallbackToFile("index.html", new StaticFileOptions
{
    FileProvider = fileProvider,
    RequestPath = ""
});

This fixed our issue (full Program.cs file). If the index.html is in a folder (e.g.: ClientApp/dist) outside wwwroot, fileProvider should be created like this.

var fileProvider = new PhysicalFileProvider(
    Path.Combine(app.Environment.ContentRootPath, "ClientApp", "dist", "index.html"));

Do not need a web.config file. Here's our .csproj file which also does not have significant change which would affect this issue.

Upvotes: 0

WhatsThePoint
WhatsThePoint

Reputation: 3635

In my case I am running an Angular app on an Azure hosted Linux box, the previously mentioned hash location strategy worked, but having the # in the URL wasn't what was wanted. Adding --spa to the configuration startup command allowed manually typing URLs. So my command is now this

pm2 serve /home/site/wwwroot/ --no-daemon --spa

More information on Azure deployment with PM2

Upvotes: 0

SimonHawesome
SimonHawesome

Reputation: 1300

Disclaimer: this fix works with Alpha44

I had the same issue and solved it by implementing the HashLocationStrategy listed in the Angular.io API Preview.

https://angular.io/docs/ts/latest/api/common/index/HashLocationStrategy-class.html

Start by importing the necessary directives

import {provide} from 'angular2/angular2';
import {
  ROUTER_PROVIDERS,
  LocationStrategy,
  HashLocationStrategy
} from 'angular2/router';

And finally, bootstrap it all together like so

bootstrap(AppCmp, [
  ROUTER_PROVIDERS,
  provide(LocationStrategy, {useClass: HashLocationStrategy})
]);

Your route will appear as http://localhost/#/route and when you refresh, it will reload at it's proper place.

Upvotes: 69

MANISH SRIVASTAVA
MANISH SRIVASTAVA

Reputation: 9

You can use location strategy, Add useHash: true in routing file.

imports: [RouterModule.forRoot(routes, { useHash: true })]

Upvotes: 0

Willie Z
Willie Z

Reputation: 506

My server is Apache, what I did to fix 404 when refreshing or deep linking is very simple. Just add one line in apache vhost config:

ErrorDocument 404 /index.html

So that any 404 error will be redirected to index.html, which is what angular2 routing wants.

The whole vhost file example:

<VirtualHost *:80>
  ServerName fenz.niwa.local
  DirectoryIndex index.html
  ErrorDocument 404 /index.html

  DocumentRoot "/Users/zhoum/Documents/workspace/fire/fire_service/dist"
  ErrorLog /Users/zhoum/Documents/workspace/fire/fire_service/logs/fenz.error.log
  CustomLog /Users/zhoum/Documents/workspace/fire/fire_service/logs/fenz.access.log combined

  <Directory "/Users/zhoum/Documents/workspace/fire/fire_service/dist">
    AllowOverride All
    Options Indexes FollowSymLinks
    #Order allow,deny
    #Allow from All
    Require all granted
  </Directory>

  Header set Access-Control-Allow-Origin "*"
  Header set Access-Control-Allow-Methods "GET, POST"
  Header set Access-Control-Allow-Credentials "true"
  Header set Access-Control-Allow-Headers "Accept-Encoding"
</VirtualHost>

No matter what server you are using, I think the whole point is finding out the ways to config the server to redirect 404 to your index.html.

Upvotes: 5

Rakesh Roy
Rakesh Roy

Reputation: 970

If you are using Apache or Nginx as a server you have to create a .htaccess (if not created before) and "On" RewriteEngine

RewriteEngine On  
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]
RewriteRule ^ /index.html

Upvotes: 7

Quy Tang
Quy Tang

Reputation: 3979

The error you are seeing is because you are requesting http://localhost/route which doesn't exist. According to Simon.

When using html5 routing you need to map all routes in your app(currently 404) to index.html in your server side. Here are some options for you:

  1. using live-server: https://www.npmjs.com/package/live-server

    $live-server --entry-file=index.html`
    
  2. using nginx: http://nginx.org/en/docs/beginners_guide.html

    error_page 404 /index.html
    
  3. Tomcat - configuration of web.xml. From Kunin's comment

    <error-page>
          <error-code>404</error-code>
          <location>/index.html</location>
    </error-page>
    

Upvotes: 152

Mukesh Kumar Bijarniya
Mukesh Kumar Bijarniya

Reputation: 466

If you are using Apache or Nginx as a server you need to create a .htaccess

<IfModule mime_module>
      AddHandler application/x-httpd-ea-php72 .php .php7 .phtml
    </IfModule>
    # php -- END cPanel-generated handler, do not edit

    <IfModule mod_rewrite.c>
        RewriteEngine on

        # Don't rewrite files or directories
        RewriteCond %{REQUEST_FILENAME} -f [OR]
        RewriteCond %{REQUEST_FILENAME} -d
        RewriteRule ^ - [L]

        # Rewrite everything else to index.html
        # to allow html5 state links
        RewriteRule ^ index.html [L]
    </IfModule>

Upvotes: 0

Shoaib Rehman Qureshi
Shoaib Rehman Qureshi

Reputation: 69

You can use this solution for mean application, I used ejs as view engine:

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.engine('html', require('ejs').renderFile);
app.use(function (req, res, next) {
    return res.render('index.html');
});

and also set in angular-cli.json

"apps": [
    {
      "root": "src",
      "outDir": "views",

it will work fine instead of

app.get('*', function (req, res, next) {
    res.sendFile('dist/index.html', { root: __dirname });
 });

its creating issue with get db calls and returning index.html

Upvotes: 3

hadiya vipul
hadiya vipul

Reputation: 116

i Just adding .htaccess in root.

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
    RewriteRule ^ - [L]
    RewriteRule ^ ./index.html
</IfModule>

here i just add dot '.'(parent directory) in /index.html to ./index.html
make sure your index.html file in base path is main directory path and set in build of project.

Upvotes: 2

Karan Khanna
Karan Khanna

Reputation: 272

I think you are getting 404 because your are requesting http://localhost/route which doesn't exist on tomcat server. As Angular 2 uses html 5 routing by default rather than using hashes at the end of the URL, refreshing the page looks like a request for a different resource.

When using angular routing on tomcat you need to make sure that your server will map all routes in your app to your main index.html while refreshing the page. There are multiple way to resolve this issue. Whichever one suits you you can go for that.

1) Put below code in web.xml of your deployment folder :

<error-page>
     <error-code>404</error-code>
     <location>/index.html</location>
</error-page>

2) You can also try using HashLocationStrategy with # in the URL for routes :

Try using:

RouterModule.forRoot(routes, { useHash: true })

Instead of:

RouterModule.forRoot(routes)

With HashLocationStrategy your urls gonna be like:

http://localhost/#/route

3) Tomcat URL Rewrite Valve : Re-write the url's using a server level configuration to redirect to index.html if the resource is not found.

3.1) Inside META-INF folder create a file context.xml and copy the below context inside it.

<? xml version='1.0' encoding='utf-8'?>
<Context>
  <Valve className="org.apache.catalina.valves.rewrite.RewriteValve" />
</Context>

3.2) Inside WEB-INF, create file rewrite.config(this file contain the rule for URL Rewriting and used by tomcat for URL rewriting). Inside rewrite.config, copy the below content:

  RewriteCond %{SERVLET_PATH} !-f
  RewriteRule ^/(.*)$ /index.html [L]

Upvotes: 1

a_k_v
a_k_v

Reputation: 1608

Just adding .htaccess in root resolved 404 while refreshing the page in angular 4 apache2.

<IfModule mod_rewrite.c>
    RewriteEngine on

    # Don't rewrite files or directories
    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^ - [L]

    # Rewrite everything else to index.html
    # to allow html5 state links
    RewriteRule ^ index.html [L]
</IfModule>

Upvotes: 1

Debargho24
Debargho24

Reputation: 51

Add imports:

import { HashLocationStrategy, LocationStrategy } from '@angular/common';

And in NgModule provider, add:

providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]

In main index.html File of the App change the base href to ./index.html from /

The App when deployed in any server will give a real url for the page which can be accessed from any external application.

Upvotes: 1

Adesh Shah
Adesh Shah

Reputation: 442

for angular 5 quick fix, edit app.module.ts and add {useHash:true} after the appRoutes.

@NgModule(
{
  imports:[RouterModule.forRoot(appRoutes,{useHash:true})]
})

Upvotes: 4

Lee Willis
Lee Willis

Reputation: 1582

I fixed this (using Java / Spring backend) by adding a handler that matches everything defined in my Angular routes, that sends back index.html instead of a 404. This then effectively (re)bootstraps the application and loads the correct page. I also have a 404 handler for anything that isn't caught by this.

@Controller         ////don't use RestController or it will just send back the string "index.html"
public class Redirect {

    private static final Logger logger = LoggerFactory.getLogger(Redirect.class);

    @RequestMapping(value = {"comma", "sep", "list", "of", "routes"})
    public String redirectToIndex(HttpServletRequest request) {
        logger.warn("Redirect api called for URL {}. Sending index.html back instead. This will happen on a page refresh or reload when the page is on an Angular route", request.getRequestURL());
        return "/index.html";
    }
}

Upvotes: 0

Malatesh Patil
Malatesh Patil

Reputation: 4665

Angular apps are perfect candidates for serving with a simple static HTML server. You don't need a server-side engine to dynamically compose application pages because Angular does that on the client-side.

If the app uses the Angular router, you must configure the server to return the application's host page (index.html) when asked for a file that it does not have.

A routed application should support "deep links". A deep link is a URL that specifies a path to a component inside the app. For example, http://www.example.com/heroes/42 is a deep link to the hero detail page that displays the hero with id: 42.

There is no issue when the user navigates to that URL from within a running client. The Angular router interprets the URL and routes to that page and hero.

But clicking a link in an email, entering it in the browser address bar, or merely refreshing the browser while on the hero detail page — all of these actions are handled by the browser itself, outside the running application. The browser makes a direct request to the server for that URL, bypassing the router.

A static server routinely returns index.html when it receives a request for http://www.example.com/. But it rejects http://www.example.com/heroes/42 and returns a 404 - Not Found error unless it is configured to return index.html instead

If this issue occurred in production, follow below steps

1) Add a Web.Config file in the src folder of your angular application. Place below code in it.

<configuration>
<system.webServer>
<rewrite>
    <rules>
    <rule name="Angular Routes" stopProcessing="true">
        <match url=".*" />
        <conditions logicalGrouping="MatchAll">
        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
        </conditions>
        <action type="Rewrite" url="/" />
    </rule>
    </rules>
</rewrite>
</system.webServer>
</configuration>

2) Add a reference to it in angular-cli.json. In angular-cli.json put Web.config in assets block as shown below.

"assets": [
    "assets",
    "favicon.ico",
    "Web.config"
  ],

3) Now you can build the solution for production using

ng build --prod

This will create a dist folder. The files inside dist folder are ready for deployment by any mode.

Upvotes: 3

Rahul Singh
Rahul Singh

Reputation: 19640

This is not a permanent Fix for the problem but more like a workaround or hack

I had this very same issue when deploying my Angular App to gh-pages. First i was greeted with 404 messages when Refreshing my pages on gh-pages.

Then as what @gunter pointed out i started using HashLocationStrategy which was provided with Angular 2 .

But that came with it's own set of problems the # in the url it was really bad made the url look wierd like this https://rahulrsingh09.github.io/AngularConcepts/#/faq.

I started researching out about this problem and came across a blog. I tried giving it a shot and it worked .

Here is what i did as mentioned in that blog.

You’ll need to start by adding a 404.html file to your gh-pages repo that contains an empty HTML document inside it – but your document must total more than 512 bytes (explained below). Next put the following markup in your 404.html page’s head element:

<script>
  sessionStorage.redirect = location.href;
</script>
<meta http-equiv="refresh" content="0;URL='/REPO_NAME_HERE'"></meta>

This code sets the attempted entrance URL to a variable on the standard sessionStorage object and immediately redirects to your project’s index.html page using a meta refresh tag. If you’re doing a Github Organization site, don’t put a repo name in the content attribute replacer text, just do this: content="0;URL='/'"

In order to capture and restore the URL the user initially navigated to, you’ll need to add the following script tag to the head of your index.html page before any other JavaScript acts on the page’s current state:

<script>
  (function(){
    var redirect = sessionStorage.redirect;
    delete sessionStorage.redirect;
    if (redirect && redirect != location.href) {
      history.replaceState(null, null, redirect);
    }
  })();
</script>

This bit of JavaScript retrieves the URL we cached in sessionStorage over on the 404.html page and replaces the current history entry with it.

Reference backalleycoder Thanks to @Daniel for this workaround.

Now the above URL changes to https://rahulrsingh09.github.io/AngularConcepts/faq

Upvotes: 0

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657937

Angular by default uses HTML5 pushstate (PathLocationStrategy in Angular slang).
You either need a server that processes all requests like it were requesting index.html or you switch to HashLocationStrategy (with # in the URL for routes) https://angular.io/docs/ts/latest/api/common/index/HashLocationStrategy-class.html

See also https://ngmilk.rocks/2015/03/09/angularjs-html5-mode-or-pretty-urls-on-apache-using-htaccess/

To switch to HashLocationStrategy use

update for >= RC.5 and 2.0.0 final

import {HashLocationStrategy, LocationStrategy} from '@angular/common';

@NgModule({
  declarations: [AppCmp], 
  bootstrap: [AppCmp],
  imports: [BrowserModule, routes],
  providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]
]);

or shorter with useHash

imports: [RouterModule.forRoot(ROUTER_CONFIG, {useHash: true}), ...

ensure you have all required imports

For the new (RC.3) router

<base href="."> 

can cause 404 as well.

It requires instead

<base href="/">

update for >= RC.x

bootstrap(AppCmp, [
  ROUTER_PROVIDERS,
  provide(LocationStrategy, {useClass: HashLocationStrategy})
  // or since RC.2
  {provide: LocationStrategy, useClass: HashLocationStrategy} 
]);

import {provide} from '@angular/core';
import {  
  PlatformLocation,  
  Location,  
  LocationStrategy,  
  HashLocationStrategy,  
  PathLocationStrategy,  
  APP_BASE_HREF}  
from '@angular/common';  

update for >= beta.16 Imports have changed

import {BrowserPlatformLocation} from '@angular/platform-browser';

import {provide} from 'angular2/core';
import {
  // PlatformLocation,
  // Location,
  LocationStrategy,
  HashLocationStrategy,
  // PathLocationStrategy,
  APP_BASE_HREF}
from 'angular2/router';
import {BrowserPlatformLocation} from 'angular2/src/router/location/browser_platform_location';

< beta.16

import {provide} from 'angular2/core';
import {
  HashLocationStrategy
  LocationStrategy,
  ROUTER_PROVIDERS,
} from 'angular2/router';

See also https://github.com/angular/angular/blob/master/CHANGELOG.md#200-beta16-2016-04-26 breaking-changes

Upvotes: 50

William R
William R

Reputation: 37

The answer is quite tricky. If you use a plain old Apache Server (or IIS), you get the problem because the Angular pages do not exist for real. They are "computed" from the Angular route.

There are several ways to fix the issue. One is to use the HashLocationStrategy offered by Angular. But a sharp sign is added in the URL. This is mainly for compatibility with Angular 1 (I assume). The fact is the part after the sharp is not part of the URL (then the server resolves the part before the "#" sign). That can be perfect.

Here an enhanced method (based on the 404 trick). I assume you have a "distributed" version of your angular application (ng build --prod if you use Angular-CLI) and you access the pages directly with your server and PHP is enabled.

If your website is based on pages (Wordpress for example) and you have only one folder dedicated to Angular (named "dist" in my example), you can do something weird but, at the end, simple. I assume you have stored your Angular pages in "/dist" (with the according <BASE HREF="/dist/">). Now use a 404 redirection and the help of PHP.

In your Apache configuration (or in the .htaccess file of your angular application directory), you must add ErrorDocument 404 /404.php

The 404.php will start with the following code:

<?php
$angular='/dist/';
if( substr($_SERVER['REQUEST_URI'], 0, strlen($angular)) == $angular ){
    $index = $_SERVER['DOCUMENT_ROOT'] . $angular . "index.html";
    http_response_code(200);
    include $index;
    die;
}

// NOT ANGULAR...
echo "<h1>Not found.</h1>"

where $angular is the value stored in the HREF of your angular index.html.

The principle is quite simple, if Apache does not find the page, a 404 redirection is made to the PHP script. We just check if the page is inside the angular application directory. If it is the case, we just load the index.html directly (without redirecting): this is necessary to keep the URL unchanged. We also change the HTTP code from 404 to 200 (just better for crawlers).

What if the page does not exist in the angular application? Well, we use the "catch all" of the angular router (see Angular router documentation).

This method works with an embedded Angular application inside a basic website (I think it will be the case in future).

NOTES:

  • Trying to do the same with the mod_redirect (by rewriting the URLs) is not at all a good solution because files (like assets) have to be really loaded then it is much more risky than just using the "not found" solution.
  • Just redirecting using ErrorDocument 404 /dist/index.html works but Apache is still responding with a 404 error code (which is bad for crawlers).

Upvotes: 0

TimTheEnchanter
TimTheEnchanter

Reputation: 3671

2017-July-11: Since this is linked from a question having this problem but using Angular 2 with Electron, I'll add my solution here.

All I had to do was remove <base href="./"> from my index.html and Electron started reloading the page again successfully.

Upvotes: 1

kaifeong
kaifeong

Reputation: 21

You can try out below. It works for me!

main.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

...
export class MainComponent implements OnInit {
    constructor(private router: Router) {
        let path: string = window.location.hash;
        if (path && path.length > 0) {
            this.router.navigate([path.substr(2)]);
        }
    }

    public ngOnInit() { }
}

You can further enhance path.substr(2) to split into router parameters. I'm using angular 2.4.9

Upvotes: 2

Divyanshu-systematix
Divyanshu-systematix

Reputation: 21

The best solution of resolve the "router-not-working-on-reloading-the-browser" is that we should use spa-fall back. If you are using angular2 application with asp.net core then we need to defined it on "StartUp.cs" page. under MVC routs. I am attaching the code.

 app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
            routes.MapSpaFallbackRoute("spa-fallback", new { controller = "Home", action = "Index" });
        });

Upvotes: 0

nkeymeulen
nkeymeulen

Reputation: 11

If you want to use PathLocationStrategy :

  • Wildfly configuration :
    • Create undertow-handlers.conf file to be placed in the WEB-INF
    • Content : (exclude your rest endpoints !)
      • regex['(./overview/?.?$)'] and not regex['(./endpoints.)'] -> rewrite['/index.html']
      • regex['(./deployments/?.?$)'] and not regex['(./endpoints.)'] -> rewrite['/index.html']

Single page application with Java EE/Wildfly: server-side configuration

Upvotes: 1

felipecrp
felipecrp

Reputation: 1069

If you want to be able to enter urls in browser without configuring your AppServer to handle all requests to index.html, you must use HashLocationStrategy.

The easiest way to configure is using:

RouterModule.forRoot(routes, { useHash: true })

Instead of:

RouterModule.forRoot(routes)

With HashLocationStrategy your urls gonna be like:

http://server:port/#/path

Upvotes: 12

don505
don505

Reputation: 162

This is not the right answer but On-refresh you can redirect all dead calls to Homepage by sacrificing 404 page it's a temporary hack just replay following on 404.html file

<!doctype html>
<html>
    <head>
        <script type="text/javascript">
            window.location.href = "http://" + document.location.host;
        </script>
    </head>
</html>

Upvotes: 0

Lievno
Lievno

Reputation: 1041

I checked in angular 2 seed how it works.

You can use express-history-api-fallback to redirect automatically when a page is reload.

I think it's the most elegant way to resolve this problem IMO.

Upvotes: 1

Devin McQueeney
Devin McQueeney

Reputation: 1287

I wanted to preserve the URL path of sub pages in HTML5 mode without a redirect back to index and none of the solutions out there told me how to do this, so this is how I accomplished it:

Create simple virtual directories in IIS for all your routes and point them to the app root.

Wrap your system.webServer in your Web.config.xml with this location tag, otherwise you will get duplicate errors from it loading Web.config a second time with the virtual directory:

<configuration>
    <location path="." inheritInChildApplications="false">
    <system.webServer>
        <defaultDocument enabled="true">
            <files>
                <add value="index.html" />
            </files>
        </defaultDocument>
    </system.webServer>
  </location>
</configuration>

Upvotes: 1

angmerica
angmerica

Reputation: 797

I had the same problem with using webpack-dev-server. I had to add the devServer option to my webpack.

Solution:

// in webpack
devServer: {
    historyApiFallback: true,
    stats: 'minimal'
}

Upvotes: 7

Jim Hume
Jim Hume

Reputation: 31

For those of us struggling through life in IIS: use the following PowerShell code to fix this issue based on the official Angular 2 docs (that someone posted in this thread? http://blog.angular-university.io/angular2-router/)

Import-WebAdministration
# Grab the 404 handler and update it to redirect to index.html.
$redirect = Get-WebConfiguration -filter "/system.WebServer/httperrors/error[@statusCode='404']" -PSPath IIS:\Sites\LIS 
$redirect.path = "/index.html"
$redirect.responseMode = 1
# shove the updated config back into IIS
Set-WebConfiguration -filter "/system.WebServer/httperrors/error[@statusCode='404']" -PSPath IIS:\Sites\LIS -value $redirect

This redirects the 404 to the /index.html file as per the suggestion in the Angular 2 docs (link above).

Upvotes: 2

Angular University
Angular University

Reputation: 43117

This is a common situation in all router versions if you are using the default HTML location strategy.

What happens is that the URL on the browser bar is a normal full HTML url, like for example: http://localhost/route.

So when we hit Enter in the browser bar, there is an actual HTTP request sent to the server to get a file named route.

The server does not have such file, and neither something like express is configured on the server to handle the request and provide a response, so the server return 404 Not Found, because it could not find the route file.

What we would like is for the server to return the index.html file containing the single page application. Then the router should kick in and process the /route url and display the component mapped to it.

So to fix the issue we need to configure the server to return index.html (assuming that is the name of your single page application file) in case the request could not be handled, as opposed to a 404 Not Found.

The way to do this will depend on the server side technology being used. If its Java for example you might have to write a servlet, in Rails it will be different, etc.

To give a concrete example, if for example you are using NodeJs, you would have to write a middleware like this:

function sendSpaFileIfUnmatched(req,res) {
    res.sendFile("index.html", { root: '.' });
}

And then register it at the very end of the middleware chain:

app.use(sendSpaFileIfUnmatched);

This will serve index.html instead of returning a 404, the router will kick in and everything will work as expected.

Upvotes: 23

Related Questions