Skip to content

Instantly share code, notes, and snippets.

@zishe
Last active May 30, 2020 05:06
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zishe/6925726 to your computer and use it in GitHub Desktop.
Save zishe/6925726 to your computer and use it in GitHub Desktop.
Using grunt and Angular with pushstate support!
title author layout categories
Using grunt and angular with pushstate support
Blackjid
post
grunt
angularjs
yeoman

One of the neat things about angularjs routes system is the ability to use the html5 pushstate apis to remove the hash (#) from the url when creating a single page sites.

For this to work you need to tell your server that every request must be rewrited to /index.html, this way angular is goint to take care of the rest.

If you are using yeoman 1.0 to scaffold your app, you should have a Gruntfile.js in the root of your project. You can add some rewrites using the connect-modrewrite module. (Note: Remember to add the connect-modrewrite module to your package.json)

npm install connect-modrewrite

Require the module in your Gruntfile.js adding

var modRewrite = require('connect-modrewrite');

Then in your livereload configuation add a section in the middlewares

livereload: {
    options: {
      port: 9000,
      // Change this to '0.0.0.0' to access the server from outside.
      hostname: '0.0.0.0',
      middleware: function (connect) {
        return [
          modRewrite([
            '!(\\..+)$ / [L]'
          ]),
          lrSnippet,
          mountFolder(connect, '.tmp'),
          mountFolder(connect, yeomanConfig.app)
        ];
      }
    },
}

Here in the modRewrite middleware you can pass an array of rules that you want to rewrite (check here to see how to write this rules)

You have to be careful because if you are requesting a .js file or .css file you should serve that file and not rewrite the request. For this I use a regex that matches every url that has one of the most common extensions used in the web. (Note the ! at the beggining of the regex means that will invert the match)

'!\\.?(js|css|html|eot|svg|ttf|woff|otf|css|png|jpg|git|ico) / [L]'

Additional things

Enable pushstate in angular

You will need to enable the pushstate method within angular routes. In the app.js file inject the locationProvider and the html5Mode line in the config section.

.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
    $locationProvider.html5Mode(true);  // Add this line
    $routeProvider
      .when('/', { ...

Absolute urls

Also I was calling my scripts and css files with relatives urls and this only work if you start navigating from the root page.

Change this

<script src="components/angular/angular.js"></script>

to this

<script src="/components/angular/angular.js"></script>

Note the leading slash. Now the url is absolute.

@phishy
Copy link

phishy commented Oct 22, 2013

I totally want to use this but keep getting, "Warning: lrSnippet is not defined Use --force to continue."

@jjt
Copy link

jjt commented Nov 5, 2013

@phishy: Did you use generator-angular to scaffold your app? If so, what version? (look at the top of your Gruntfile)

My generator-angular version is 0.4.0 and Gruntfile.js:4 is:

var lrSnippet = require('connect-livereload')({ port: LIVERELOAD_PORT });

@zishe
Copy link
Author

zishe commented Nov 12, 2013

Yes, doesn't work with new Grunt version, and I can't resolve it yet. It should works with:

      livereload: {
        options: {
          open: true,
          base: [
            require('connect-modrewrite')(['!(\\..+)$ / [L]']),
            '.tmp',
            '<%= yeoman.app %>'
          ]
        }
      },

But now in Grunt file you can't use functions, only strings.

@jjt
Copy link

jjt commented Nov 15, 2013

This is working for me in generator-angular#0.6.0-rc1. Just add the middleware function to the connect.options object

connect: {
  options: {
    // ...
    // Modrewrite rule, and connect.static(path) for each path in target's base
    middleware: function (connect, options) {
      var optBase = (typeof options.base === 'string') ? [options.base] : options.base;
      return [require('connect-modrewrite')(['!(\\..+)$ / [L]'])].concat(
        optBase.map(function(path){ return connect.static(path); }));
    }
  }
}

More discussion about this on issue #433.

@charleslouis
Copy link

Hi there !

Thanks for these tips !

This worked for me thanks to @Kryx

https://stackoverflow.com/questions/24283653/angularjs-html5mode-using-grunt-connect-grunt-0-4-5/24384293#24384293
It seems things have change a bit and I haven't been able to make it work after several attemps until I found this.

In a nut shell

Insert modRewrite(['^[^\\.]*$ /index.html [L]']), in liverload.options

To wrap it up here :

run npm install connect-modrewrite --save-dev

Then declare this at the top of Gruntfile.js
var modRewrite = require('connect-modrewrite');
inside of
module.exports = function (grunt) {

Insert modRewrite(['^[^\\.]*$ /index.html [L]']), in liverload.options :

livereload: {
    options: {
        open: true,
        middleware: function (connect) {
            return [
                modRewrite(['^[^\\.]*$ /index.html [L]']),
                connect.static('.tmp'),
                connect().use(
                    '/bower_components',
                    connect.static('./bower_components')
                ),
                connect.static(appConfig.app)
            ];
        }
    }
},

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment