Sumit patel
Sumit patel

Reputation: 3903

Refreshing page shows file not found error how to resolve using [angular js + NodeJS/ExpressJS ]

Note: I tried all questions & answer related this topic. Like This and I tried related questions and try to solve it but not success.

I am building angularJS web-app. It's purely based on AngularJS/HTML5 and NodeJS/ExpressJS and Database side use mongo DB then this problem occurs.

I want To '#' remove in url and I refresh page then display my current page .But right now display "Not Found 404".I use like this $locationProvider.html5Mode(true); and <base href="/" /> but I am not success.

I know remove # in URL solution is $locationProvider.html5Mode(true);and <base href="/" /> But i use NodeJS/ExpressJS then I can't use.

My Url

http://localhost:3000/Tutorial/Routing/StateProvider/index.html#/Setting/StudenList

I want to URL

http://localhost:3000/Tutorial/Routing/StateProvider/index.html/Setting/StudenList

Notes:

Without this Solution $locationProvider.html5Mode(true);and <base href="/" /> But i use NodeJS/ExpressJS Then I want to remove # and refreshing page issue solve

Code

Folder Structure directive .my code very long then i mange snippet (inside html & js ). Not run snippet because i insert all my code just for understand what is mistake my code.

sample2(refreshissue) [Project Name]
-- Public
    -- Tutorial
        --Directive
            -index.html
        --Routing
           --StateProvider
                -Account.html
                -index.html
                -Setting.html
                -StudentListing.html
                -studentDetails.html
                -StateProviderController.js
        --Validation
          -index.html
 -index.html

--  StateProviderController.js
---------------------------------------------------------------------------------
var myapp= angular.module('myapp2',["ui.router"]);
myapp.config(function($stateProvider,$urlRouterProvider,$locationProvider,$urlMatcherFactoryProvider){
    $urlMatcherFactoryProvider.strictMode(false);
    $stateProvider
.state('TutorialHome', {
            url:'/index',
            templateUrl:'/index.html'
        })
        .state('Profile',{
            url:'/Profile',
            templateUrl:'Profile.html'
        })
        .state('Account',{
            url:'/Account',
            templateUrl:'Account.html'
        })
        .state('Setting',{
            url:'/Setting',
            templateUrl:'Setting.html'
        })
        .state('Setting.StudenListing', {
            url:'/StudenList',
            views: {
                'StudenListing': {
                    templateUrl: 'StudenListing.html',
                    controller:'StudentListingData'
                }
            }
        })
        .state('Setting.StudenListing.StudentList',{
            url:'/StudenList/:StudentID',
            /* templateUrl: 'StudentDetails.html',
            controller:'StudentDetails'*/
            views:{
               'StudentDetails': {
                   templateUrl: 'StudentDetails.html',
                   controller:'StudentDetails'
              }
            }
        })
    ;

   // $urlRouterProvider.otherwise('/index');
 //$locationProvider.html5Mode(true);

});
myapp.controller('StateProviderCtrl',function($scope){
    $scope.message ="Welcome To State Provider Page";
    $scope.Home = function()
    {
        window.open('/',"_self");
    }
});

myapp.controller('StudentListingData',function($scope,$http){
    console.log('test');
$http.get('/StudenRecordData').success(function(response){
   // console.log(response);
    $scope.StudentRecorddata =response;
})
});

myapp.controller('StudentDetails',function($scope,$http,$stateParams){
    $scope.StudentID = $stateParams.StudentID;
    //console.log( $scope.StudentID);

    $http.get('/StuentRecordSearch/'+ $stateParams.StudentID).success(function(response){
        //console.log(response);
        $scope.StuentDetails =response[0];
    })

});
==================================================================================================================================================================
----  app.js
---------------------------------------------------------------------------------
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var url =require('url');

var index = require('./routes/index');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');


//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', index);
/*var basepathArray = ['/Tutorial/Routing/StateProvider/','/Tutorial/Validation/','/Tutorial/Directive/'];
app.get('/!*',function(req,res){
  var basePath ="";
  for(var i=0;i<=basepathArray.length-1;i++)
  {
    if(req.originalUrl.search(basepathArray[i]) != -1){
      basePath =basepathArray[i];
      break;
    }
  }
  if(basePath!="")
  {
    res.sendFile(path.resolve('public'+basePath+'index.html'));
  }
  else {
    res.sendFile(path.resolve('public/index.html'));
  }



});*/

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});



module.exports = app;
=================================================================================
 --  Account.html
---------------------------------------------------------------------------------
<h1>Account  page</h1>

=================================================================================
--  index.html
---------------------------------------------------------------------------------
<!DOCTYPE html>
<html ng-app="myapp2">
<title>Index | Angular Js</title>
<base href="/Tutorial/Routing/StateProvider/" />
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<!--<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-route.js"></script>-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.3.2/angular-ui-router.js"></script>
<script src="StateProviderController.js"></script>

<body ng-controller="StateProviderCtrl">
<nav class="navbar navbar-default row">
    <div class="container-fluid">
        <div class="navbar-header">
            <a class="navbar-brand" ui-sref="TutorialHome"> State Routing</a>
        </div>
            <ul class="nav navbar-nav">
                <li><a ui-sref="Profile">Profile</a></li><!--State Transition on click-->
                <li><a ui-sref="Account">Account</a></li><!--State Transition on click-->
                <li><a ui-sref="Setting">Setting</a></li><!--State Transition on click-->
                <li style="float: right;" ><a ng-click="Home()"> Home</a></li><!--State Transition on click-->
            </ul>

    </div>
</nav>


<div class="container" ng-controller="StateProviderCtrl">
    <!-- we use ui-view instead of ng-view -->
    <!--{{message}}<br>-->
    <ui-view></ui-view>

</div>


</body>
</html>
=================================================================================
--  Profile.html
--------------------------------------------------------------------------------
<h1>Profile  page</h1>

=================================================================================
--  Setting.html
---------------------------------------------------------------------------------
<div>
    <h1>Setting page</h1>
    <strong>This page shows Nested states & views. Click on below links to see Nested states in action.</strong><br>
    <ul>
        <li><a ui-sref="Setting.StudenListing">Show Listing</a></li>
    </ul>
    <div class="container">

        <div class="row">
            <div class="col-sm-12" style="background-color:beige;display: inline-block">
                <div ui-view="StudenListing"></div>
            </div>
        </div>
    </div>

</div>
  <!--  <div ui-view="Descriptions"></div><br>
    <div ui-view="Price"></div>-->
=================================================================================
--  StudentListing.html
---------------------------------------------------------------------------------
    <!--<ui-view></ui-view>-->


    <div class="row">
        <div class="col-sm-6" style="background-color:beige;">

            <h2>Student Listing</h2>
            <p>All Talented Student List</p>
            <table class="table" >
                <thead>
                <tr>
                    <th>Name</th>
                    <th>Eduction</th>
                    <th>Email ID</th>
                    <th>Details <!--<div ui-view="StudentDetails"></div>--></th>
                </tr>
                </thead>
                <tbody>
                <tr ng-repeat="data in StudentRecorddata">
                    <td>{{data.Name}}</td>
                    <td>{{data.Eduction}}</td>
                    <td>{{data.Email}} </td>
                    <td><button type="button" class="btn btn-info" ui-sref="Setting.StudenListing.StudentList({StudentID:$index})">View Details</button> </td>
                </tr>

                </tbody>
            </table>
        </div>
        <div class="col-sm-6" style="background-color:beige;">
           <!-- <div ui-view="StudenListing"></div>-->
            <div ui-view="StudentDetails"></div>
        </div>
    </div>
=================================================================================
-- studentDetails.html
------------------------------------------------------------------------------<div>
    <h2>Student Details </h2>
    <br>
    <form class="form-horizontal">
        <div class="form-group">
            <label class="control-label col-sm-2" for="email">Stuent Id:</label>
            <div class="col-sm-10">
                <p class="form-control-static">{{StudentID}}</p>
            </div>
        </div>
        <div class="form-group">
           <label class="control-label col-sm-2" for="email">Name:</label>
            <div class="col-sm-10">
                <p class="form-control-static">{{StuentDetails.Name}}</p>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-sm-2" for="pwd">Age:</label>
            <div class="col-sm-10">
                <p class="form-control-static">{{StuentDetails.Age}}</p>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-sm-2" for="email">Eduction:</label>
            <div class="col-sm-10">
                <p class="form-control-static">{{StuentDetails.Eduction}}</p>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-sm-2" for="pwd">Email:</label>
            <div class="col-sm-10">
                <p class="form-control-static">{{StuentDetails.Email}}</p>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-sm-2" for="email">MobileNumber:</label>
            <div class="col-sm-10">
                <p class="form-control-static">{{StuentDetails.MobileNumber}}</p>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-sm-2" for="pwd">Gender:</label>
            <div class="col-sm-10">
                <p class="form-control-static">{{StuentDetails.Gender}}</p>
            </div>
        </div>

    </form>
</div>

Upvotes: 21

Views: 5899

Answers (4)

Gandalf the White
Gandalf the White

Reputation: 2465

Well... this issue is something everyone might have encountered when starting with node.js along with angular.

First Issue is the '#' in url and like everyone suggested just do this

$locationProvider.html5Mode(true);

Now the refresh issue will happen, expected... again.

Why it happens?

The request is sent to node.js server and there is nothing in place there to prevent it.

How to solve it?

Just make sure the corresponding trailing requests are mapped to the main angular view. If you have only one ng-app for whole program then just redirect to main view.

Add something like this on your node.js server

app.get('/', function(req, res) {
  res.sendFile(__dirname + '/index.html');
});

app.get('/*', function(req, res) {
  res.sendFile(__dirname + '/index.html');
});

Here index.html is my main view which is linked to the main controller file where code for routing exists. If I am trying to load /about it'll go to the controller and then the controller will load the specific view.

This is very easy way to solve this problem.

Upvotes: 0

anis programmer
anis programmer

Reputation: 999

You have to change in application-configuration.js file as follows:

please find this portion: "indexController"

var indexController = function ($scope, $rootScope, $http, $location, blockUI) {
            var baseUrl="/Tutorial"; //here it will be your site name
}

just add this baseUrl in your url link.

Upvotes: 0

Taku
Taku

Reputation: 5928

This is what I have for my app.

From the API docs for HTML5 mode

if (HTML5 mode is) enabled and requireBase are true, and a base tag is not present, an error will be thrown when $location is injected.

So, you need the base tag. I've tried without the base tag and requireBase to be false but it still seems to throw an error for me.

index.html

<head>
    <base href="/">
</head>

app.route.js[Angular.js]

app.config(function($stateProvider, $urlRouterProvider, $locationProvider) {

    // This removes '#' and makes URL pretty
    $locationProvider.html5Mode({
        enabled: true,
        requireBase: true
    });

    // Default path if nothing is matched. State would become 'landing'
    $urlRouterProvider.otherwise('/');

    $stateProvider
        .state('landing', {
            url: '/',
            templateUrl: "/dashboard/landing/templates/index.html",
            controller: 'LandingController'
        }).state('about', {
            url: '/about',
            templateUrl: "/dashboard/about/templates/index.html",
            controller: 'AboutController'
        });

});

Once you redirect yourself from 'landing' to 'about' state and the route changes to /about, the URL is only changed on the browser and the server does not know of this. So, when you refresh, the server does not know what /about path means. Thus, you need to redirect /about path (or any path that belongs to angular's routes) to index.html and then angular redirects you back to /about automagically.

app.js[Express.js]

//  dashboard route should deliver templates instead of loading angular JS app so this should come before the next one
app.use('/dashboard', express.static(__dirname + '/public/dashboard'));

// put all the API methods here and separate from the redirection to `index.html`
app.use('/api', routes.apiRoutes); 

// all the paths that do not start with `/dashboard` or `/api` is/should be angular route and thus, redirect back to `index.html`
app.get('*', function (req, res) {
    res.sendFile(__dirname + '/public/index.html');
});

Side note

On another app, I've seen an issue where refreshing the browser appends a trailing slash at the end of the path (/) and after the redirection, Angular app could not recognize the path from the url /about vs /about/ so it redirected me back to the landing page.

I resolved this by adding this in app.route.js

$urlMatcherFactoryProvider.strictMode(false);

I hope this helps

Upvotes: 5

Ramesh Rajendran
Ramesh Rajendran

Reputation: 38673

This is because the web server receiving the request looks for a resource matching the full url on the server, which doesn't exist because the angular portion of the url refers to a route in your angular application and needs to be handled in the client browser

The way to fix this is by rewriting all virtual urls to the main angular index.html file

AngularJS + NodeJS/ExpressJS - Routes to prevent 404 error after page refresh in html5mode

var express = require('express');
var path = require('path');
var router = express.Router();

// serve angular front end files from root path
router.use('/', express.static('app', { redirect: false }));

// rewrite virtual urls to angular app to enable refreshing of internal pages
router.get('*', function (req, res, next) {
    res.sendFile(path.resolve('app/index.html'));
});

module.exports = router;

AngularJS + IIS - URL Rewrite Rule to prevent 404 error after page refresh in html5mode (for apache click here)

<rewrite>
  <rules>
    <rule name="AngularJS" 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>

Referred from

additional way to fix this

https://stackoverflow.com/a/34817349/2218635

Upvotes: 10

Related Questions