9

Hi have created one angularJS application with yeoman, grunt and bower. I have enabled the html5Mode for the application. And its working. But, when I refresh a page (localhost:9000/login), it says

Cannot GET /login //or any url I type or refresh

Here is application structure

MainApp
|
|__app
|  |
|  |__bower_components
|  |
|  |__scripts
|  | |
|  | |__ app.js
|  | |
|  | |__contollers -- login.js, home.js, register.js
|  | |
|  | |__service -- js files
|  | |
|  | |__styles -- CSS files
|  | |
|  | |__views -- main.html, login.html, register.html,home.html
|  |
|  |__ index.html
|
|__ node_modules
|
|__ bower.json, Gruntfile.js, karma-conf.js, karma-e2e.conf.js, package.json

Here is my app.js routing

'use strict';    
var superClientApp=angular.module('mainApp', ['ngCookies']);

//Define Routing for app
superClientApp.config(['$routeProvider', '$locationProvider',
  function($routeProvider,$locationProvider) {
    $routeProvider
    .when('/login', {
        templateUrl: 'login.html',
        controller: 'LoginController'
    })
    .when('/register', {
        templateUrl: 'register.html',
        controller: 'RegisterController'
      })
    .when('/home', {
       templateUrl: 'views/home.html',
       controller: 'homeController'
    })
    .otherwise({
       redirectTo: '/login'
    });
    $locationProvider.html5Mode(true);
}]);

Here is my part of Gruntfile.js

'use strict';
var LIVERELOAD_PORT = 35729;
var lrSnippet = require('connect-livereload')({ port: LIVERELOAD_PORT });
var mountFolder = function (connect, dir) {
  return connect.static(require('path').resolve(dir));
};
var proxySnippet = require('grunt-connect-proxy/lib/utils').proxyRequest;

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {
  require('load-grunt-tasks')(grunt);
  require('time-grunt')(grunt);

  // configurable paths
  var yeomanConfig = {
    app: 'app',
    dist: 'dist'
  };

  try {
    yeomanConfig.app = require('./bower.json').appPath || yeomanConfig.app;
  } catch (e) {}

  grunt.initConfig({
    yeoman: yeomanConfig,
    watch: {
      coffee: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'],
        tasks: ['coffee:dist']
      },
      coffeeTest: {
        files: ['test/spec/{,*/}*.coffee'],
        tasks: ['coffee:test']
      },
      styles: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
        tasks: ['copy:styles', 'autoprefixer']
      },
      livereload: {
        options: {
          livereload: LIVERELOAD_PORT
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },
    autoprefixer: {
      options: ['last 1 version'],
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost'
      },
      proxies: [
        {
            context: '/serverApp',
            host: 'localhost',
            port: '8080',
            https: false,
            changeOrigin: false
        }
      ],
      livereload: {
        options: {
          middleware: function (connect) {
            return [
              lrSnippet,
              proxySnippet,
              mountFolder(connect, '.tmp'),
              mountFolder(connect, yeomanConfig.app)
            ];
          }
        }
      },

I have gone through this SO question. And based on the accepted answer, I changed my Gruntfile.js to below.

'use strict';
var LIVERELOAD_PORT = 35729;
var lrSnippet = require('connect-livereload')({ port: LIVERELOAD_PORT });
var mountFolder = function (connect, dir) {
  return connect.static(require('path').resolve(dir));
};
var proxySnippet = require('grunt-connect-proxy/lib/utils').proxyRequest;

var urlRewrite = require('grunt-connect-rewrite');

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {
  require('load-grunt-tasks')(grunt);
  require('time-grunt')(grunt);

  // configurable paths
  var yeomanConfig = {
    app: 'app',
    dist: 'dist'
  };

  try {
    yeomanConfig.app = require('./bower.json').appPath || yeomanConfig.app;
  } catch (e) {}

  grunt.initConfig({
    yeoman: yeomanConfig,
    watch: {
      coffee: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'],
        tasks: ['coffee:dist']
      },
      coffeeTest: {
        files: ['test/spec/{,*/}*.coffee'],
        tasks: ['coffee:test']
      },
      styles: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
        tasks: ['copy:styles', 'autoprefixer']
      },
      livereload: {
        options: {
          livereload: LIVERELOAD_PORT
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },
    autoprefixer: {
      options: ['last 1 version'],
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        base: 'app',
        middleware: function(connect, options) {
            // Return array of whatever middlewares you want
            return [
              // redirect all urls to index.html in build folder
              urlRewrite('app', 'index.html'),

              // Serve static files.
              connect.static(options.base),

              // Make empty directories browsable.
              connect.directory(options.base)
            ];
          }
      },
      proxies: [
        {
            context: '/serverApp',
            host: 'localhost',
            port: '8080',
            https: false,
            changeOrigin: false
        }
      ],
      livereload: {
        options: {
          middleware: function (connect) {
            return [
              lrSnippet,
              proxySnippet,
              mountFolder(connect, '.tmp'),
              mountFolder(connect, yeomanConfig.app)
            ];
          }
        }
      },

But still I am getting the same error when I refresh the page. How to solve this?

Community
  • 1
  • 1
Coder
  • 6,948
  • 13
  • 56
  • 86
  • 1
    localhost:9000/#!/login ? – michael Jan 08 '14 at 11:18
  • Sorry I don't get you – Coder Jan 08 '14 at 11:20
  • 2
    in pure html5 mode you can not call the urls directly. you first need to call localhost:9000, so that the browser can configure this mode. after that a klick on href="login" will include your desired template. – michael Jan 08 '14 at 12:02
  • I am calling that way only. – Coder Jan 08 '14 at 13:17
  • have you tried remove $locationProvider.html5Mode(true); ? i guess this is what you want... – michael Jan 08 '14 at 13:38
  • For now, I have commented that line so my application works. Then the url will be like `localhost:9000/#/login`. I want to remove the `#`. Thats why I am using that line. Do you have any other idea to remove that and works the application when I refresh also? – Coder Jan 08 '14 at 13:48
  • here is an example from the angular documentation: http://docs.angularjs.org/api/ngRoute.$route#example please scroll down and click on the link "Scarlet Letter". now make a page reload - you will see what you get so far - the site will not load. you first need to load the web site, configre html5 model - then you can use this html5 feature – michael Jan 08 '14 at 13:53

6 Answers6

4

You must have a server side solution to redirect all non resolving traffic to index.html

I will share an htaccess version of this and a node.js version. I have also implemented this in spring-boot, but that was a little bit more involved.

HTACCESS

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /the-base-from-index.html/


    RewriteEngine on
    RewriteCond %{HTTP:X-Requested-With} !XMLHttpRequest$
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule . index.html [L]
</IfModule>

In order for this to work your apache config must have mod_rewrite enabled and also allow override all. The rewrite will work in apache config as well.

There is also a condition that allows you to return 404 on ajax request. In order for that to work you must register a pre-flight header to all $http request signifying that they are xmlhttp requests. something like:

 myapp.config(function($httpProvider) {
      $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
 });

NODEJS

var express = require('express');
var bodyParser = require('body-parser')
var path = require('path')
var app = express();

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

app.listen(9090);
console.log("Node Server Listening on port 9090");

This is dirt simple, and could be improved. Namely with a check on the req headers for the xmlhttp header, similarly to what the htaccess example is doing.

These get the general idea across. Basically any traffic that would result in a 404 gets served index.html instead. This allows you to continue to serve up static content, but your routes (as long as they don't actually exist on the disk at that location) will return index.html.

Warning! without a solution like the preflight header on all ajax request, any unresolved template request will result in an infinite loop and likely lock up your browser.

Jeremythuff
  • 1,518
  • 2
  • 13
  • 35
2

By now, this must be a common issue with Angular to be dealt with for routing to work.

I found similar issues on SO, which suggest the same html5 config to workaround the hashbang method - see AngularJS: how to enable $locationProvider.html5Mode with deeplinking Also a tutorial at scotch.io https://scotch.io/quick-tips/pretty-urls-in-angularjs-removing-the-hashtag can be a start.

But most importantly, I remember that this is the one that did the trick.

https://gist.github.com/leocaseiro/4305e06948aa97e77c93

I run a Apache Web server. I don't recollect at what level (globally or per app) I did the configuration, but best would be to consult the web server docs, Apache's was good.

Community
  • 1
  • 1
Mahesh
  • 954
  • 8
  • 18
0

This is happening because when you press refresh, your browser will try to retrieve /login from the server, not via your client side route provider. I assume /login is returning a 404 in your case.

You need to get the server that's actually serving your files to serve /index.html even though the request was made for /login. How you do this depends on your server side setup. You can do this with either dynamic scripting or a rewrite rule.

If you can't control the server in this way then htmlmode is probably not for you sadly.

glorat
  • 324
  • 4
  • 7
0

add 'views/login.html' in side your templateUrl hope this will work

keshav
  • 46
  • 1
0

add this code in server.js after all the routes

app.use('/*',function(req, res) {
    res.sendfile(__dirname + '/public/index.html');
});
Aman Kumar
  • 100
  • 1
  • 8
-2

did you try using absolute paths in the $routeProvider?

.when('/login', {
  templateUrl: '../app/scripts/views/login.html',
  controller: 'loginController'
},
...
Ganonside
  • 1,329
  • 1
  • 10
  • 15