Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular routing in HTML5mode with Node.js

I know there are other answers to this but they seem to have caveats.

This one causes a redirect, which can be fatal for my front-end app which uses Mixpanel, and a double-load of Mixpanel will a Maximum Call Stack Size Exceeded error in the browser.

This one uses sendFile which I personally cannot get to work. Trying to diagnose that, I'm just advised to use express.static().

Currently my code looks like this:

home.js - Some routes, the last one intended to be the front-end site.

var indexPath = path.resolve( __dirname, '../' + process.env.PUBLIC_FOLDER )

router.get( '/:user/:stream/:slug', function( req, res, next ) {

    if ( req.headers['user-agent'].indexOf( 'facebook' ) != -1 ) {
        // stuff to handle the Facebook crawler
    } else return next()
})

router.get( '/*', function( req, res ) {
    express.static( indexPath )
})

server.js - configuring node/express

app = express();

app 
    .use( morgan( 'dev' ) )
    .use(bodyParser.urlencoded( { limit: '50mb', extended: true } ) )
    .use( bodyParser.json( { limit: '50mb' } ) )
    .use( '/api', require('./routes/usersRoute.js') )
    .use( '/', require( './routes/home' ) )
    .on( 'error', function( error ){
       console.log( "Error: " + hostNames[i] + "\n" + error.message )
       console.log( error.stack )
    })

http
    .createServer( app ).listen( process.env.PORT )
    .on( 'error', function( error ){
       console.log( "Error: " + hostNames[i] + "\n" + error.message )
       console.log( error.stack )
    })

Some more info

The reason you can see I'm trying to use express.static() is because when I use res.sendfile() I get a problem like this one where the console says Unexpected token '<'. Unfortunately the answer doesn't specify an exact fix and neither does the questioner who says they fixed the problem but don't share an answer.

In my trial and error I have added some more to express, like this

.use( '/app/app.js', express.static( indexPath + '/app/app.js' ) )
.use( '/app/views', express.static( indexPath + '/app/views' ) )
.use( '/app/controllers', express.static( indexPath + '/app/views' ) )
.use( '/app/directives', express.static( indexPath + '/app/views' ) )
.use( '/app/vendor', express.static( indexPath + '/app/vendor' ) )
.use( '/js', express.static( indexPath + '/js' ) )
.use( '/css', express.static( indexPath + '/css' ) )
.use( '/fonts', express.static( indexPath + '/fonts' ) )
.use( '/images', express.static( indexPath + '/images' ) )
.use( '/api', require('./routes/usersRoute.js') )
.all( '/*', require( './routes/home' ) )

And in my home.js routes files added this

router.get( '/*', function ( req, res ) {
    res.status( 200 ).set( { 'content-type': 'text/html; charset=utf-8' } )
    .sendfile( indexPath + '/index.html' )
})

And in the browser I can see all my files are loading, but with the < error above. I see this /*/ route is being called hundreds of times when I do a refresh so I think the .use( '...', ... ) configurations are being ignored.


Here is another example requested by Jonas below.

var indexPath = path.resolve( __dirname, process.env.PUBLIC_FOLDER )

mongoose.connect( process.env.MONGOLAB_URI )

app = express();

app 
    .use( morgan( 'dev' ) )
    .use(bodyParser.urlencoded( { limit: '50mb', extended: true } ) )
    .use( bodyParser.json( { limit: '50mb' } ) )
    .use( '/api', require('./routes/usersRoute.js') )
    .use( '/', require( './routes/home.js' ) )
    .use( express.static( indexPath ) )
    .on( 'error', function( error ){
       console.log( "Error: " + hostNames[i] + "\n" + error.message )
       console.log( error.stack )
    })

I have also done the same without the .use( '/', require( './routes/home.js' ) ) line to try narrow down any problem, but it's the same result. The page will load if I have the # in the URL, but the browser will remove the # (so far so good). But if I press refresh, or put in the URL manually, sans-hashbang, it will give an error like Cannot GET /home/splash, where /home/splash is whatever path I'm going to.

like image 333
Noah Avatar asked Jul 09 '15 20:07

Noah


People also ask

Can I use angular with NodeJS?

Yes. Node. js required for Angular 2 or Angular apps.

CAN node and angular run on same port?

Is putting angular in the dist folder what makes it run in the same port with express? Yes.

What is routing in angular7?

Advertisements. Routing basically means navigating between pages. You have seen many sites with links that direct you to a new page. This can be achieved using routing.


2 Answers

You might be using the express middleware in the wrong way. Looking at the documentation tells me for example that the following code

.use( '/images', express.static( indexPath + '/images' ) )

Serves any file under the folder indexPath + '/images' with such URLs:

http://localhost:3000/images/kitten.jpg
http://localhost:3000/images/logo.png
http://localhost:3000/images/whatever.jpg

Is that what you would expect it to do?

My suggestion is to change from

.use( '/', require( './routes/home' ) )

to

.use(express.static( indexPath ))

Because according to the documentation, it will serve all static files under the indexPath folder from the root path, aka. with no prefix.

I think that is all I can help with the input you gave so far. If that doesn't do the trick, maybe you can share some small code example that I can use to reproduce it on my own.

UPDATE

Ok. I've tried to create a simple example of it. I made it work in the following way. My directory structure is like this:

|- index.js
|- public/
|---- index.html
|---- test.html
|---- main.js
|---- angular.js
|---- angular-route.js

index.js is the node server. Pay attention to the order of the routing. I also added some /home/login example to make it clear how to add other server routes that should not go through angular.

var http = require('http');
var express = require('express');
var app = express();

app
    .use(express.static('public'))
    .get('/home/login', function (req, res) {
        console.log('Login request');
        res.status(200).send('Login from server.');
    })
    .all('/*', function ( req, res ) {
        console.log('All');
        res
            .status( 200 )
            .set( { 'content-type': 'text/html; charset=utf-8' } )
            .sendfile('public/index.html' );
    })
    .on( 'error', function( error ){
       console.log( "Error: \n" + error.message );
       console.log( error.stack );
    });

http
    .createServer( app ).listen( 8080 )
    .on( 'error', function( error ){
       console.log( "Error: \n" + error.message );
       console.log( error.stack );
    });

console.log('Serving app on port 8080');

index.html is pretty simple.

<!doctype html>
<html>
<head>
    <title>Angular HTML5 express test</title>
    <script type="text/javascript" src="angular.js"></script>
    <script type="text/javascript" src="angular-route.js"></script>
    <script type="text/javascript" src="main.js">
    </script>
</head>
<body ng-app="app">
    <div ng-view></div>
</body>
</html>

main.html just adds some content. Important is the link to /test

<div>
    <h1>This is just a {{val}}</h1>
    <button ng-click="clicked()">Click me!</button>
    <span>You clicked {{counter}} times</span>
    <a href="/test">test me!</a>
</div>

test.html is actually irrelevant

<div>
    <h4>What a beautiful day to test HTML5</h4>
</div>

main.js is doing the core angular html5 work

angular.module('app', ['ngRoute'])
    .config(function($locationProvider) {
        $locationProvider
            .html5Mode({
                enabled: true, // set HTML5 mode
                requireBase: false // I removed this to keep it simple, but you can set your own base url
            });
    })
    .config(function($routeProvider) {
        $routeProvider
            .when('/test', {templateUrl: 'test.html', controller: function() {
                console.log('On /test.');
            }})
            .when('/', {templateUrl: 'main.html', controller: 'MyTestCtrl'})
            .otherwise('/');
    })
    .controller('MyTestCtrl', function ($scope) {
        self = $scope;
        self.val = 'TeSt';
        self.counter = 0;
        var self = self;
        self.clicked = function() {
            self.counter++;
        };
    });
like image 137
Jonas Avatar answered Oct 06 '22 12:10

Jonas


instead of:

router.get( '/*', function( req, res ) {
    express.static( indexPath )
})

do

router.get( '/:anyreq', function( req, res ) {
    express.static( indexPath )
})

just keep it at end of routes file.

like image 23
Paweł Smołka Avatar answered Oct 06 '22 11:10

Paweł Smołka