Node Express server deep linking not working with AngularJS html5Mode - javascript

(Update: I solved the problem. Just look at the end of the question)
I am running with this problem that seems trivial to me, but I am very frustrated because I am not able to figure it out:
I scaffolded an Angular application using yeoman generator-angular. I need to use the html5mode of Angular to get rid of the hashtag (please, see app.js below). I am using a node express server (see server.js) to run the app built with grunt build.
As required, I added the option in the server to redirect to index.html when accessing the app from any specific route. It works with one level of "routing", i.e., localhost:8080/research, but it does not work for two "levels" or more, i.e., localhost:8080/research/human. In this case, when refreshing the browser, I get this error:
The stylesheet http://localhost:8080/research/styles/vendor.8089f103.css was not loaded because its MIME type, "text/html", is not "text/css". human
The stylesheet http://localhost:8080/research/styles/main.e7eff4cf.css was not loaded because its MIME type, "text/html", is not "text/css". human
SyntaxError: expected expression, got '<' vendor.01f538ae.js:1:0
SyntaxError: expected expression, got '<'
I have searched everywhere, I have tried all sort of options, but I am not able to fix it. I would really appreciate some help, please!
app.js
angular
.module('testAngularApp', [
'ngRoute',
'ngTouch',
'ngAnimate',
'ngSanitize',
'angulartics'
])
.config(function ($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider
.when('/', {
templateUrl: 'views/mainFrontpage.html',
controller: 'MainFrontpageController'
})
.when('/research', {
templateUrl: 'views/research.html',
controller: 'ResearchController'
})
.when('/research/human', {
templateUrl: 'views/research-human.html',
controller: 'ResearchController'
})
.when('/research/fly', {
templateUrl: 'views/research-fly.html',
controller: 'ResearchController'
})
.otherwise ({
templateUrl: 'views/notyetready.html',
});
});
server.js
'use strict';
var express = require('express');
var path = require('path');
var morgan = require('morgan');
var fs = require('fs');
var currentDir = process.cwd();
var app = express();
var staticStats = fs.statSync( currentDir + '/dist');
if (staticStats.isDirectory()) {
app.use('/', express.static(currentDir + '/dist'));
// Here I have tried many different combinations
app.use("/styles", express.static(__dirname + "dist/styles"));
app.use("/scripts", express.static(__dirname + "dist/scripts"));
app.use("/views", express.static(__dirname + "dist/views"));
app.use("/fonts", express.static(__dirname + "dist/fonts"));
app.use("/templates", express.static(__dirname + "dist/templates"));
app.use("/images", express.static(__dirname + "dist/images"));
app.all('/*', function(req, res, next) {
// Just send the index.html for other files to support HTML5Mode
res.sendFile('dist/index.html', { root: __dirname });
});
var server = app.listen(8080);
console.log('Node Express server listening on http://%s:%d', server.address().address,8080);
}
else {
console.log('No /dist folder, did not start the server');
}
Update: Solution
Thanks to the comments of the users, I asked the question in a different way and found the solution that make it works here. That is, the <base href="/"> tag must be located before the <link rel="stylsheet"..> tags (what a hard time I got for such a stupid thing!)

Why not use nested routing in angular routing like this ?
https://github.com/angular-ui/ui-router

I wanted this to be just a comment but I can't yet, Have you tried placing the html5 mode below your routes ?
Can remove the if statement if you do not need it.
.when('/research/fly', {
templateUrl: 'views/research-fly.html',
controller: 'ResearchController'
})
.otherwise ({
templateUrl: 'views/notyetready.html',
});
if(window.history && window.history.pushState){
$locationProvider.html5Mode(true);
}
});

Maybe try using a different pattern to match against that sounds like the cause of the problem
app.all('/**/*', function(req, res, next)
However I would ask why are you serving the static files with node + express? If all you want is a static file server for local development why not try grunt-serve
Then if you want to serve the static files on a server you can use nginx which works really well with angular in html5 mode

Related

Trouble with $routeProvider in page served by Express.js

I am trying to build my first Express.js/Angular application but am having trouble with $routeProvider on the page being served by the Express.js application. Here is the server.js:
var express = require('express');
var PORT = process.env.PORT || 3000;
var app = express();
app.get('/', function (req, res) {
res.sendFile(__dirname + '/client/views/index.html');
});
app.listen(3000, function () {
console.log("I'm listening");
});
The index.html page renders and includes the following Angular code:
var app = angular.module('app', ['ngRoute']);
app.config(function ($routeProvider) {
$routeProvider
.when('/main', {
templateUrl:'/client/views/main.html'
});
});
Yet when navigating to localhost:3000/#/main, nothing is injected into the <ng-view>
Any advice would be greatly appreciated.
Having encountered this issue before I found a solution by telling express to serve files statically.
app.use(express.static('client'));
This tells express to send files from the 'client' directory as-is. This is used for files such as images, CSS & client side JavaScript. When referencing a file statically, paths are relative to the specified static directory so /client/views/main.html would be /views/main.html.
You can view the documentation for the express static middle ware here.

ui route, node, express, 404 html files

I can't figure this out for the life of me. I'm using Node, Express, Angular and ui.route. What I'm trying to accomplish is the following,
have index.html as a master
have some.html as partials
I setup the application to use ejs instead of jade (don't know if that matters at all)?
The error I'm getting is that it can't find localhost:3000/views/partial-home.html 404, I tried all types of different paths in my routing,
(function(){
var app = angular.module('routerApp', ['ui.router']);
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/about');
$stateProvider
.state('home-about', {
url: '/home-about',
templateUrl: '/views/partial-home.html'
})
.state('about', {
});
});
}());
I do not have anything particular in my app.js file to handle html or in my route/index.js, currently I think it's using the index.ejs as well. What steps am I missing in using html and why could the error be with it not finding the partial-home.html
//routes/index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
There are posts about this topic but none kinda sums up my two issues with using html files and why the 404. Appreciate all help I can get. Thanks!
You still need to 'mount' this router to the app.
app.use('/', router);
The router will be relative to the path that you mount it to, in this case the root /. You can mount it to any path and the router will be relative to that path. e.g.
app.use('/bar', router);
And your router 'mini-app' will be invoked when the application gets (or any other HTTP method) /bar/ path.
Hey you can set your app view engine as html such that it can render html file.
var ejs=require("ejs");
app.set('views',//folder path of views);
app.engine('html', ejs.renderFile);
app.set('view engine', 'html');
I got the solution. It's just one of those gotchas. I was looking into my app.js, I had all the routing correct as Arbel was helping me with.
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
So I was looking though the file and it hit me when I seen this line, right that this is a static resource.
app.use(express.static(path.join(__dirname, 'public')));
So I put my html files into public/partials and that was it.
Now my route look like this
$stateProvider
.state('home-about', {
url: '/home-about',
templateUrl: '/partials/partial-home.html'
});

Node / Angular app Uncaught SyntaxError: Unexpected token <

I'm following this node/angular tutorial and am getting the following errors:
I'm bootstrapping my app through node, which renders index page:
module.exports = function(app) {
app.get('*', function(req, res) {
res.sendfile('./public/index.html');
...
});
Which renders:
<html ng-app="DDE">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/main.js"></script>
</head>
<body>
This is the index page
<div ng-view></div>
I'd like Node to handle the initial page load, but Angular to handle the rest of the routing. The problem is here: It doesn't seem my angular routing is working. I put a self-exec fn run() in there to test, but it's not being called.
I'm simply trying to test display the testpage.html template:
app.js file:
angular
.module('DDE', [
'ngRoute'
])
.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/test', {
run : (function() {
alert('hit');
})(),
templateUrl: '../html/partials/testpage.html'
}).
otherwise({
redirectTo: '/test'
});
}
]);
The angular error isn't very helpful. I'm not sure what Unexpected token < means as I cannot find where I've added an extra < anywhere.
EDIT:
app.get('/', function(req, res) {
res.send('./public/index.html');
});
It should be able to find the stuff in bower components as the pathing is correct:
root/bower_components/angular/angular.js
root/bower_components/angular-route/angular-route.js
You are missing some settings in your app file on the server to handle all of the requests being made to the server. Here is a basic sample working express file, which you can modify to fit your environment:
var express = require('express');
var app = express();
app.use('/js', express.static(__dirname + '/js'));
app.use('/bower_components', express.static(__dirname + '/../bower_components'));
app.use('/css', express.static(__dirname + '/css'));
app.use('/partials', express.static(__dirname + '/partials'));
app.all('/*', function(req, res, next) {
// Just send the index.html for other files to support HTML5Mode
res.sendFile('index.html', { root: __dirname });
});
This file uses express.static to serve any content which is in the specific directory regardless of name or type. Also keep in mind that Express use statements are processed in order, and the first match is taken, any matches after the first are ignored. So in this example, if it isn't a file in the /js, /bower_components, /css, or /partials directory, the index.html will be returned.
I was able to resolve my issue with the unexpected token by moving my server.js file from the DIST directory into the directory above.
So my folder/file structure looks like this:
webroot - root folder
server.js
dist (folder)
browser (subfolder)
server (subfolder)

AngularJS routing Server side support (NodeJS+express)

I am using NodeJS + expressJS on server and AngularJS on client
AngularJS controller have the following routes:
app.config(function($routeProvider,$locationProvider) {
$routeProvider
.when('/field1/:id', {
controller: 'ctrl1',
templateUrl: "/view/mainpages/field1.html"
})
.when('/field2/:id', {
controller: 'ctrl2',
templateUrl: "/view/mainpages/field2.html"
})
........... many many
.when('/', {
controller: 'ctrl0',
templateUrl: "/view/mainpages/index.html"
})
.otherwise({redirectTo : '/'});
NodeJS static:
app.all('/*', express.static(__dirname + '/client'));
How to make the server redirects all the static pages on their paths relative to the root, in the case when the call goes directly through this link.
example:
server:3000/field1/23 must call server:3000/client/index.html
and index must upload JS like server:3000/js/cntr1.js
Upd: I found a solution to the problem. But it looks ugly as a crutch
app.get('/field1/([0-9]+$)', function(req, res, next) {
req.url='/index.html';
next();
});
app.use('/field1', express.static(__dirname + '/client'));
Perhaps there is a more elegant solution?
In expressjs, you can define the route using regular expression pattern matching. That means, anything under a specific set will always return the same page from the server side, and you will let angular decide what exactly to display or do which action.
Its not a great idea to redirect everything at / to same page as you need to manage your assets and they exist on the / part too , instead its a good idea to use a subdir which is easy to manage
Following snippet will redirect everything under the app path to the same page on server side
app.get('/app/*', function(req, res) {
res.send('app_index');
})
if you do not want to be limited to one app dir, instead you have a number of different root level dirs, all of which need to point to the same page, you can do something like this
app.get('/(app|server|client|form|dashboard)/*', function(req, res) {
res.send('app_index');
})

Angular routing doesn't work when URL is directly visited in browser but works when clicked to?

I have an angular app with a directory structure
app
..views
....partials
......main.jade
......foo.jade
....index.jade
and routes defined like:
'use strict';
angular.module('myApp', [
'ngCookies',
'ngResource',
'ngSanitize',
'ngRoute',
'firebase',
'myApp.config'
])
.config(function ($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider
.when('/', {
templateUrl: '/partials/main',
controller: 'MainCtrl'
})
.when('/foo/:fooName', {
templateUrl: '/partials/foo',
controller: 'FooCtrl'
})
.otherwise({
redirectTo: '/'
});
});
I'm using express on the server side and the relevant code is:
// server.js
app.configure('development', function(){
app.use(express.static(path.join(__dirname, '.tmp')));
app.use(express.static(path.join(__dirname, 'app')));
app.use(express.errorHandler());
});
app.configure('production', function(){
app.use(express.favicon(path.join(__dirname, 'public/favicon.ico')));
app.use(express.static(path.join(__dirname, 'public')));
});
app.get('/', routes.index);
app.get('/partials/:name', routes.partials);
//routes.js
exports.index = function(req, res){
res.render('index');
};
exports.partials = function(req, res){
var name = req.params.name;
res.render('partials/' + name);
};
The main route "/" loads fine and when i click to "/foo/bar" the partial view foo.jade loads as expected. However, when I try visiting "/foo/bar" directly in the URL i get a 404 response from Express "cannot GET /foo/bar" which makes sense since there's no route like this defined in express. However, I thought this was the whole point of defining the angular router..i.e. it's supposed to intercept this request and actually ask for "/partials/foo".
I tried adding
//redirect all others to the index (HTML5 history)
app.get('*', routes.index);
but it didnt solve the issue and seemed to be catching even the requests for static js assets and responding with the contents of index.html which is pretty bad.
I'm sure I'm doing something wrong. How can I fix things so that I can directly visit the URLs?
The reason routing is behaving like this is html5mode turned on.
Notice the line: $locationProvider.html5Mode(true);
You need to understand that when you try to access "/foo/bar" directly your browser sends HTTP GET request to this URL. When you try to access this url via link clicking, like you said, Angular is intercepting this action and calls History.pushState that only updates browser's link.
What you need to do in order to make html5mode routing work is implementing url rewrite. Every request has to be redirected to your index file that bootstraps AngularJS application, but this needs to be done on your server. Angular will render the desired page by itself.
Check out connect mod rewrite grunt task that does exacly that.
You can also you this middleware directly.

Categories