How do I properly render Angular routes using the Express framework? - angularjs

I have installed the express-generator package with npm and used the express myApp command to generate my app. I am having trouble working with the routes. I understand that there are Express routes, which are used for the backend stuff, and Angular routes, which are for the frontend. The problem: none of my routes besides the index are rendering. So my file structure is:
/myApp
/bin
/node_modules
/public
/images
/js
/controllers
app.js
/stylesheets
/routes
index.js
/views
error.jade
index.jade
layout.jade
about.jade
app.js
package.json
My 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 routes = require('./routes/index');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// uncomment after placing your favicon in /public
//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('/', routes);
// 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 handlers
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
My routes/index.js:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'myApp' });
});
module.exports = router;
From what I understand, the above serves up the index and then from there the Angular routes take over and load partial templates. My Angular routes are in public/js/app.js:
angular.module('myApp', ['ngRoute'])
.config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider){
$routeProvider
.when('/', {
templateUrl: '/views/index.jade',
controller: 'IndexCtrl'
})
.when('/about', {
templateUrl: '/views/about.jade',
controller: 'AboutCtrl'
})
.otherwise({ redirectTo: '/index' });
}]);
So when I have an anchor tag link to my about page in the layout.jade template:
doctype html
html(lang='en', ng-app='myApp')
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
base(href='/')
body
header.header
h1.title MY TITLE
ul.navbar
li
a.about(href='/about') About
block content
And I load up my server and click the "about" link, I get a 404 error. In my console I see a 404 for index.jade AND for about.jade, but my home page loads the content of index.jade anyway.
I have tried changing the href in my anchor tag from /about to #/about, and then instead of giving me a 404 when I click the link, the url changes to localhost:3000/#/about but the content of the page doesn't change at all. It still shows the content of index.jade.
Also not sure if relevant, when I hit localhost:3000 in the browser it automatically adds /#/, so the full url shows http://localhost:3000/#/.
Any help will be greatly appreciated! Also please let me know if I should provide any more information/code. Thank you!

It looks like the routes for the views (error.jade, and about.jade) are not setup in your routes.js file. If you go to the link http://localhost:3000/views/about.jade do you see anything?

Related

Express - enabling catch all for Angular 1.x html5Mode with multiple router files is not working

Okay so this question originates from this post, which is also my question but sorta moved onto this problem.
Now the problem is, my angular app works but when it comes to Node routes, like /login, the browser thinks it's an Angular route. The browser does detect it as a Node route when I refresh the whole page but not when I am navigating the site once Angular routing has kicked in.
Long story short, here are my route files:
require('./routes/routes.js')(express, app);
require('./routes/default.js')(express, app);
routes.js file:
module.exports = function(express, app){
var router = express.Router();
router.get('/', function(req, res){
res.render('index');
});
router.get('/login', function(req, res){
if (req.isAuthenticated()){
res.render('index');
} else {
res.render('login');
}
});
default.js:
module.exports = function(express, app, passport, Promise){
var defaultRouter = express.Router();
defaultRouter.get('/*', function(req, res, next) {
res.render('./views/index.html');
});
};
Your help will be very much appreciated.
Thanks
I gave up on making this work on the root URL so I added /index to the baseURL like this:
index.html file:
<head>
<base href="/index/">
</head>
The, I added a catch all to the routes.js file, as below:
module.exports = function(express, app){
var router = express.Router();
router.get('*', function(req, res, next) {
if(req.url == '/' || req.url == '/login'){
next();
} else {
res.sendfile('./views/index.html');
}
});
router.get('/', function(req, res){
res.render('index');
});
router.get('/login', function(req, res){
if (req.isAuthenticated()){
res.render('index');
} else {
res.render('login');
}
});
With the angular file of course having this:
$locationProvider.html5Mode({
enabled: true,
requireBase: true
}).hashPrefix('');
And there. It works like a charm now.
I hope this helps whoever are in my shoes.

AngularJS - Redirect if url isn't correct

I'm building an app in angularjs with different states and have added a $urlRouterProvider.otherwise("/"); to redirect people is they don't go to a state that I have set up.
Though I am having an issue when they go to an URL beyond the ones I have set up.
So for example, the following state is profile:
.state('profile', {
url: '/profile',
templateUrl: 'partials/profile.html',
controller: 'profileCtrl',
data: {
requiresLogin: true
},
resolve: {
$title: function() { return 'Profile'; }
}
});
Lets say I were to go to an url with /prof instead of /profile, I will get a redirect to home aka ("/").
Here is where the problem shows up: if I'd go to an url as followed: /profile/test, I will get alot of error in my console (image below)
Extra Info(from comment):
I'll also give the server side code seeing the problem might be there with a redirect:
// Init Express Web Framework
var express = require('express');
var app = express();
var path = require('path');
// Set view engine to EJS & set views directory
app.set('view engine', 'ejs');
app.set('views', path.resolve(__dirname, 'client', 'views'));
app.use(express.static(path.resolve(__dirname, 'client')));
// Database Connection
var mongoose = require('mongoose');
var configDB = require('./server/config/database.js');
mongoose.connect(configDB.url);
var bodyParser = require('body-parser');
app.use(bodyParser.json());
// Main route
app.get('/', function(req, res){
res.render('index.ejs');
});
// API
var api = express.Router();
require('./server/routes/capture')(api);
app.use('/api', api);
// Set routes to other pages
app.get('/*', function(req, res){
res.render('index.ejs');
});
// Port Settings
app.listen(process.env.PORT || 3000, process.env.IP);
console.log('Listening on port ' + process.env.PORT);
and here is my workspace
Redirecting URL's that are not listed get redirected. Url's that are listed (for example /dashboard, /profile, ...) when adding something to the link, they don't get redirected. I have encountered these errors when I didn't add the controller to the main html, but that's just the thing.. I don't want to be able to surf to these links. All links that haven't been stated in app.js should be redirected.
Is there any way to fix this?
EDIT - Added 404:
I added a catch 404 in my server.js using this code:
// catch 404 and forwarding to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
This is the result:
Error: Not Found
at /home/ubuntu/workspace/server.js:31:15
at Layer.handle [as handle_request] (/home/ubuntu/workspace/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/home/ubuntu/workspace/node_modules/express/lib/router/index.js:312:13)
at /home/ubuntu/workspace/node_modules/express/lib/router/index.js:280:7
at Function.process_params (/home/ubuntu/workspace/node_modules/express/lib/router/index.js:330:12)
at next (/home/ubuntu/workspace/node_modules/express/lib/router/index.js:271:10)
at jsonParser (/home/ubuntu/workspace/node_modules/body-parser/lib/types/json.js:100:40)
at Layer.handle [as handle_request] (/home/ubuntu/workspace/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/home/ubuntu/workspace/node_modules/express/lib/router/index.js:312:13)
at /home/ubuntu/workspace/node_modules/express/lib/router/index.js:280:7
at Function.process_params (/home/ubuntu/workspace/node_modules/express/lib/router/index.js:330:12)
at next (/home/ubuntu/workspace/node_modules/express/lib/router/index.js:271:10)
at SendStream.error (/home/ubuntu/workspace/node_modules/express/node_modules/serve-static/index.js:120:7)
at emitOne (events.js:77:13)
at SendStream.emit (events.js:169:7)
at SendStream.error (/home/ubuntu/workspace/node_modules/express/node_modules/send/index.js:245:17)
Unexpected token "<" usualy means html content instead of js.
angular is not defined means your angular.js even has not to be loaded.
Check your server and Network tab in the browser.
Which urls used to load your js scripts?
What response browser gets on these urls?
Fix js urls to correct and errors will gone.
You may be wrong with your .htaccess also.
Check you not redirect existed static files inside it.
It is -f flag in the RewriteRule directive.
I take a look to your bug animation :)
Your scripts are defined as src="lib/angular.js".
This urls are relative, and on /profile page will be /profile/lib/....
What you need is absolute urls: src="/lib/angular.js"

Prerender not serving up products

I am serving up angular pages from express. I cannot get prerender to serve up rendered pages for the products:
http://www.minilolo.com/?_escaped_fragment_=/products/Lolo-Pink
But other pages like this one are OK:
http://www.minilolo.com/?_escaped_fragment_=/products
I think I may need to add some express routes, but would like to know if I am on the right track. Thanks!
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 app = express();
// uncomment after placing your favicon in /public
//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.set('view engine', 'ejs');
app.use(require('prerender-node').set('prerenderToken', 'xyz123'));
/**
* Development Settings
*/
if (app.get('env') === 'development') {
// This will change in production since we'll be using the dist folder
app.use(express.static(path.join(__dirname, '../client')));
// This covers serving up the index page
app.use(express.static(path.join(__dirname, '../client/.tmp')));
app.use(express.static(path.join(__dirname, '../client/app')));
// Error Handling
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
/**
* Production Settings
*/
if (app.get('env') === 'production') {
// changes it to use the optimized version for production
app.use(express.static(path.join(__dirname, 'dist')));
// added to serve up products pages
app.use(function(req, res) {
// Use res.sendfile, as it streams instead of reading the file into memory.
res.sendfile(__dirname + '/dist/index.html');
});
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
}
module.exports = app;
EDIT: I have tracked down the issue to prerender converting '?_escaped_fragment_=' into '#!'. Angular then doesn't know which route to use unless I have $locationProvider.hashPrefix('!') in place (which I don't want to use). I dont want the # prefix if it can be helped.
2015-09-07T12:17:17.566Z got 200 in 12713ms for http://localhost:3000/#!/products
2015-09-07T12:17:18.773Z Timed out. Sending request with HTML on the page
2015-09-07T12:17:18.785Z got 200 in 12732ms for http://localhost:3000/#!/products
2015-09-07T12:19:04.589Z getting http://localhost:3000/#!/products
As explained on the following links:
https://github.com/prerender/prerender/issues/198
https://developers.google.com/webmasters/ajax-crawling/docs/specification?hl=en
The query from the search has '?_escaped_fragment_=' at the end of the path, rather than straight after the fqdn.
http://www.minilolo.com/?_escaped_fragment_=/products/Lolo-Pink <-- not this
http://www.minilolo.com/products/Lolo-Pink?_escaped_fragment_= <-- this!

Can't navigate directly to URL Angular UI Router

This is my router file:
it's nested inside a require.js block and configured to work with Jade templates
define([
'./app',
'angular.uirouter'
], function(app, angularUIRouter) {
"use strict";
// ROUTES
app.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', function($stateProvider, $urlRouterProvider, $locationProvider) {
// loads url from the index
$urlRouterProvider.otherwise('/');
$locationProvider.html5Mode(true);
$stateProvider
.state('dashboard', {
url:'/dashboard',
views: {
'core' : {
templateUrl: '/articles/dashboard'
}
}
})
}]);
});
And this is my Express.js router file:
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res) {
res.render('main', {
title: 'Express'
});
});
router.get('/dashboard', function(req, res) {
console.log("/dashboard requested");
});
router.get('/articles/:name', function (req, res) {
var name = req.params.name;
res.render('articles/' + name);
});
module.exports = router;
When I go to localhost:3000/dashboard, it's making a GET request to the server. How do I configure Angular UI Router to handle GET requests instead of the server?
Note: I can still go to localhost:3000/articles/dashboard and see the dashboard. Also,
a(ui-sref="dashboard")
loads the dashboard correctly.
Neither angular nor ui router can not handle server GET. Angular $locationProvider html5Mode solves only client-side setting - url does not contain # and location controls also path part in URL.
Html5 mode requires server side configuration. Every requests must return application entry point - usually index.html.
For example
router.get('/dashboard', function(req, res) {
res.sendfile('path-to/index.html');
});

How can I refresh routes with angular.js, ui-router, and node.js

I have an angularjs and nodejs app with ui-router that works well from the home page. The problem is I cannot refresh any other state or type another state url in the browser and go directly to it.
I have nodejs respond to requests for the state urls with the index.html file
app.get('/*', function (req, res) {
res.sendfile('client/index.html');
});
The app gets index.html and requests all the scripts from index.html but for some reason it adds the state url in front of the request. For instance in my index.html header there is this script
<link href="css/normalize.css" rel="stylesheet">
If I refresh the browser from a state other than home, then the script is requested like this:
GET /albums/css/normalize.css
edit: I fixed this problem ^^ by adding a '/' in the link like so: href="/css/normalize.css".
Now the index file loads but angular does not send a get request for the partial file associated with the state. It only loads the index.html file.
This is my app.js file for angular
angular.module('myApp', [
'ngTouch',
'ui.router',
'ngRoute',
'ngAnimate',
'myApp.controllers',
'myApp.directives',
'myApp.restServices',
'ui.bootstrap',
'ngCookies',
'ngResource',
'ngSanitize',
'snap',
'angular.css.injector'
]).
config(['$stateProvider', '$httpProvider', '$urlRouterProvider', '$locationProvider','snapRemoteProvider', function ($stateProvider, $httpProvider, $urlRouterProvider, $locationProvider, snapRemoteProvider) {
snapRemoteProvider.globalOptions.disable = 'right';
$httpProvider.interceptors.push('authInterceptor');
$urlRouterProvider.otherwise("home");
$stateProvider
.state('home', {url: "/",templateUrl: "partials/home.html",controller: 'homeCtrl'})
.state('albums', {url: "/albums",templateUrl: "partials/albums/albums.html",controller: 'albumsCtrl'})
.config(['$locationProvider', function ($locationProvider) {
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');
}]);
This is my node server
var express = require('express'),
url = require('url');
path = require('path'),
bodyParser = require('body-parser'),
directory = require('./routes/directory'),
app = express();
app.use(function (req, res, next) {
res.setHeader('Access-Control-Allow-Origin', 'http://curtwphillips.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Accept, X-api-key, X-auth-token, Content-Type, Content-Length');
res.setHeader('Access-Control-Allow-Credentials', true);
if (req.headers && req.headers.authorization) { delete req.headers.authorization; }
next();
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static(path.join(__dirname, '/client')));
app.use(function(req, res, next){
console.log('%s %s', req.method, req.url);
next();
});
app.get('/*', function (req, res) {
res.sendfile('client/index.html');
});
app.set('port', process.env.PORT || 3000);
var server = app.listen(app.get('port'), function() {
});
I figured out the problem. I was naming some of my states with the same names as folders in my client folder.
When I tried to refresh the 'albums' state, shown here:
.state('albums', {url: "/albums",templateUrl: "partials/albums/albums.html",controller: 'albumsCtrl'})
the url "/albums" was matched to my "/client/albums" directory by this node code
app.use(express.static(path.join(__dirname, '/client')));
I fixed the issue by renaming my folders so they don't match the angular urls. This way the catchall code sends index.html. Hopefully this helps someone out there.

Resources