46

I am building an application with the help of Angular6 and facing problems in routing. All the routes are working when I click on a particular tab but whenever I refresh the current page, it is throwing 404 error. I have seen many posts regarding this issue on Stack overflow but failed to overcome from this problem.

Below is my app.module.ts

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {AppComponent} from './app.component';
import {FetchApiComponent} from './fetch-api/fetch-api.component';
import {FormsModule} from '@angular/forms';
import {HttpClientModule} from '@angular/common/http';
import {UserServiceLatest} from './fetch-latest/app.service';
import {UserServiceTop} from './fetch-top/app.service';
import {YoutubePlayerModule} from 'ngx-youtube-player';
import {SidebarComponent} from './sidebar/sidebar.component';
import {FetchLatestComponent} from './fetch-latest/fetch-latest.component';
import { FetchTopComponent } from './fetch-top/fetch-top.component'
import {UserService} from './fetch-api/app.service';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { AngularFireModule } from 'angularfire2';
import * as firebase from 'firebase';
import { firebaseConfig } from './../environments/firebase.config';
import { AngularFireDatabaseModule } from 'angularfire2/database';
import {PushService} from './push.service';

const appRoutes: Routes = [
  {
    path: '',

  component: FetchApiComponent
  }, 
{
    path: '/latest',

    component: FetchLatestComponent
  },
{
    path: '/top',

    component: FetchTopComponent
  },
{
path :'*',
component: FetchApiComponent 
}

];

firebase.initializeApp(firebaseConfig);

@NgModule({
  declarations: [
    AppComponent,
    FetchApiComponent,SidebarComponent, FetchLatestComponent, FetchTopComponent
  ],
  imports: [
    RouterModule.forRoot(appRoutes),
    BrowserModule, YoutubePlayerModule,
    FormsModule,
    AngularFireModule.initializeApp(firebaseConfig),
    AngularFireDatabaseModule,environment.production ?ServiceWorkerModule.register('firebase-messaging-sw.js'):[],ServiceWorkerModule.register('/firebase-messaging-sw.js', { enabled: environment.production }),
    HttpClientModule,environment.production ? ServiceWorkerModule.register('ngsw-worker.js') : [], ServiceWorkerModule.register('/ngsw-worker.js', { enabled: environment.production })
  ],
  providers: [UserService,UserServiceTop,UserServiceLatest,PushService],

  bootstrap: [AppComponent]
})
export class AppModule {}

Can you point me in right direction?

N Sharma
  • 33,489
  • 95
  • 256
  • 444
  • 2
    If you go manually to root path, is loading the FetchApiComponent properly? Is it only happening when refreshing? – Javier Aviles Jun 18 '18 at 11:37
  • 1
    It is only happening when I am refreshing the page. For example: My URL is: `http://localhost:4200/` , there are navigation links on the page, Fetch is one among them, when I clicks on Fetch bar, then it is making the URL as : `http://localhost:4200/fetch` and it is working fine, but when I refreshes this page, then it is throwing me the error. – N Sharma Jun 18 '18 at 11:45
  • I don't understand how is working the first time you access http://localhost:4200/, as it is the same as if you refresh the page... when you refresh, which url is trying to access? are you getting an error in the javascript console for the router? – Javier Aviles Jun 18 '18 at 12:09
  • Ok I see, that is happening due to your webserver, give me two minutes and I can give you an example in how to solve this – Javier Aviles Jun 18 '18 at 13:06

11 Answers11

47

You will see in your example url, that once you get the 404 error you can't make it work, but if you include a hash before the angular-specific url like /#latest it will work.

Why stops working when refreshing? your webserver is intercepting the GET request from your browser and is trying to go directly to the directory /latest, which doesn't exist. It doesn't know that it needs to go to /bosv2, find an angular app, and then add the small ending bit to your path which is a not-real directory but a routing for angular. In your local it would work as when you are doing ng serve, webpack webserver is prepared for this, but not the host where you are hosting the app.

By default, angular is using HTML5 style navigation, but with your current webserver settings you would need the old angularjs style (with hash#).

From here, you have two solutions:

  1. Change your webserver configuration
  2. Tell Angular to use HashLocationStrategy (perfectly valid solution), you can go old-school with the HashLocationStrategy by providing the useHash: true in an object as the second argument of the RouterModule.forRoot in the AppModule.

    @NgModule({ imports: [ ... RouterModule.forRoot(routes, { useHash: true }) // .../#/latest/ ], ...

I would say going the hash style has a couple of downsides, which may not be relevant in your scenario:

  1. It doesn't produce the clean and SEO Friendly URLs that are easier for users to understand and remember.
  2. You can't take advantage of the server-side rendering.

Hope you find this answer helpful :)

N Sharma
  • 33,489
  • 95
  • 256
  • 444
Javier Aviles
  • 7,952
  • 2
  • 22
  • 28
32

To avoid using hashed routes, you must edit your webserver configuration properly, which is the best solution. You just have to configure it so it fallbacks to index.html, which is Angular's bootstrap. Although there is no universal configuration for this, here are some:

Apache

Add a rewrite rule to .htaccess file

RewriteEngine On
# If an existing asset or directory is requested go to it as it is
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]

# If the requested resource doesn't exist, use index.html
RewriteRule ^ /index.html

Nginx

Use try_files in your location block

try_files $uri $uri/ /index.html;

IIS

Add a rewrite rule to web.config

<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="/index.html" />
      </rule>
    </rules>
  </rewrite>
</system.webServer>

GitHub Pages

You can't configure it directly, but you can add a 404 page. Copy index.html into 404.html in the same directory or add a symlink: ln -s index.html 404.html.

Firebase hosting

Add a rewrite rule.

"rewrites": [ {
  "source": "**",
  "destination": "/index.html"
} ]

Source: https://angular.io/guide/deployment#server-configuration

GTX
  • 727
  • 2
  • 13
  • 30
28

With .htaccess you can try with following way also:

<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>
bharat
  • 1,762
  • 1
  • 24
  • 32
  • 1
    A real hero! Thanks for this. – Anjana Silva Sep 30 '18 at 17:42
  • 1
    This seems to work, though on refresh am getting new these errors runtime.26209474bfa8dc87a77c.js:1 Uncaught SyntaxError: Unexpected token < polyfills.fb102f8799c272a4b157.js:1 Uncaught SyntaxError: Unexpected token < scripts.45d295297da9627b2108.js:1 Uncaught SyntaxError: Unexpected token < main.5dc5f9f560e306c1feb5.js:1 Uncaught SyntaxError: Unexpected token – Moses May 03 '19 at 12:33
  • @user3530687 - that is already in the answer - in short, that control assigns to the index.html file, rather than finding some file like passed through. – bharat May 26 '19 at 09:35
  • Many are forgetting to add RewriteRule ^ - [L], see the answer completely, dont just copy the last part – Anil Kumar Oct 30 '20 at 15:07
  • Real life hero! – PhillipJacobs May 06 '21 at 19:30
10

In app.module.ts

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

After import add following line to providers.

{provide: LocationStrategy, useClass: HashLocationStrategy}

ex:

providers: [AuthService, 
            AuthGuard, 
            FlxUiDataTable,
            {provide: LocationStrategy, useClass: HashLocationStrategy}]

This will solve your issue. Read Documentation here.

Nimezzz
  • 1,814
  • 14
  • 15
9

Add .htaccess file to your src folder.

.htaccess file

<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>

Load .htaccess file in your build directory dist by adding it to assets in angular.json

"assets": [
     "src/favicon.ico",
     "src/assets",
     "src/.htaccess"
],
Bhadresh Arya
  • 754
  • 7
  • 6
2

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]

Karan Khanna
  • 272
  • 2
  • 12
1

Starting with Apache 2.4, you can use the FallbackResource directive instead of rewriting, so it will look like:

FallbackResource /index.html

If you have a different base href (say, /awesomeapp), change it for:

<Location /awesomeapp>
    FallbackResource /awesomeapp/index.html
</Location>
mrjoops
  • 38
  • 3
1

If you are using cpanel then it is easy to solve this issue.

Go to Advanced Options

Step 1: Go to Error Pages.

Step 2: Copy your index.html code and paste it in 404.shtml.

That's it technically all your routes are redirected to index.html file. That's what angular wants :) and everything will work normal.

Here are some reference links

Namecheap Error Page Config

Godaddy Error Page config

SaiSurya
  • 1,046
  • 8
  • 14
  • this is good a a "fallback" option. But in case of 404 it will not load the same page/route, but the default route. – Coen Damen May 03 '21 at 06:32
0

In my case i did following thing

Method 1 :

in your app.module.ts import below thing

import { HashLocationStrategy, LocationStrategy } from '@angular/common';
@NgModule({
  declarations: [...],
  imports: [...],
  providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}],
  bootstrap: [AppComponent]
})

and build with

ng build --base-href /[PROJECT_NAME]/

method 2 :

for nginx,

nano /etc/nginx/sites-available/default

add follwing line in location block

location /[PROJECT_NAME] {
  try_files $uri $uri/ /[PROJECT_NAME]/index.html;
}

sudo service nginx restart

and build with

ng build --base-href /[PROJECT_NAME]/

Renish Gotecha
  • 2,232
  • 22
  • 21
0

Refetch data on same URL navigation

imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload' })],

Reshan Maduka
  • 281
  • 2
  • 5
0

Make an .htaccess file and add this code; routes will start working:

RewriteEngine On
 
  RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
  RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
  RewriteRule ^ - [L]
  
RewriteRule ^ /index.html
Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77