AngularJS: Relative link paths are broken when html5mode(true) is on - angularjs

I've been searching and I've found "solutions" to this problem, yet I still can't get this to work right.
The Scenario:
I'm building an Angular (version 1.2) website with the UI Router and running it on a Node server on localhost. I'm trying to make it have "pretty" url's with the $locationProvider and by turning html5(true) on. My website works fine when clicking through it, but when I try to navigate to a relative link path or refresh the link path the page breaks. I also intend to deploy this webapp to Heroku when completed:
RELATIVE LINK PATH:
http://localhost:8000/locksmith-services
PAGE OUTPUT RESULT
Cannot GET /locksmith-services
Steps I've taken:
1.) In my "index.html" < head >, I've set my base url to:
<base href="/"></base>
2.) In my app.js file (for Angular), I have it written as follows:
// App Starts
angular
.module('app', [
'ui.router',
'ngAnimate',
'angular-carousel'
])
.config(['$urlRouterProvider', '$stateProvider', '$locationProvider', function($urlRouterProvider, $stateProvider, $locationProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state('home', {
url: '/',
templateUrl: 'pages/home.html',
controller: 'homeCtrl'
})
.state('services', {
url: '/locksmith-services',
templateUrl: 'pages/locksmith-services.html',
controller: 'servicesCtrl'
})
.state('locations', {
url: '/locksmith-locations',
templateUrl: 'pages/locksmith-locations.html'
})
.state('payment', {
url: '/locksmith-payment',
templateUrl: 'pages/locksmith-payment.html'
})
// use the HTML5 History API
$locationProvider.html5Mode(true);
}])
3.) In my navigation, I have my html written as:
<div class="wrapper">
<a ui-sref="home">
<img src="images/logo.png" class="logo" alt="Austin Texas Locksmith" />
</a>
</div>
<nav class="row navigation">
<a class="mobile33" ui-sref="services" ui-sref-active="active" class="active">Services</a>
<a class="mobile33" ui-sref="locations" ui-sref-active="active">Locations</a>
<a class="mobile33" ui-sref="payment" ui-sref-active="active">Payment</a>
</nav>
4.) My server.js file (node server)
var express = require('express');
var app = express();
app.use(express.static(__dirname + '/front'));
var port = process.env.PORT || 8000;
app.listen(port);
What would be the best solution? Thanks in advance for your help.

Thanks to #trehyu for helping me get to this answer.
Like he wrote, I needed something setup on my server.js file that redirects the user to my "index.html" file.
So depending on your file structure...
BEFORE (not working)
var express = require('express');
var app = express();
app.use(express.static(__dirname + '/front'));
var port = process.env.PORT || 8000;
app.listen(port);
AFTER (working)
var express = require('express');
var app = express();
app.use('/js', express.static(__dirname + '/front/js'));
app.use('/build', express.static(__dirname + '/../build'));
app.use('/css', express.static(__dirname + '/front/css'));
app.use('/images', express.static(__dirname + '/front/images'));
app.use('/pages', express.static(__dirname + '/front/pages'));
app.all('/*', function(req, res, next) {
// Just send the index.html for other files to support HTML5Mode
res.sendFile('/front/index.html', { root: __dirname });
});
var port = process.env.PORT || 8000;
app.listen(port);
I hope this helps someone else!

When HTML5mode is set to true, you need something setup on your server that automatically redirects the user to your index page, so that the AngularJS UI Router can take over from there.
The reason for this is that without the hash (#) in the URL, it takes it as a literal URL and tries to navigate there when you refresh or paste the url directly.
I'm not very familiar with Node so not sure how you would do that, but there is a FAQ page on the UI Router GitHub that should help you get started: https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-configure-your-server-to-work-with-html5mode

Related

Express + Angular routing bug

I am working on learning authentication using Angular 1 and Express. I've got my backend set up for testing purposes, and i'm now working on the front end. I've got basic routes set up.
This is the core to my front end:
function router($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('home', {
url: '/',
templateUrl: 'views/home/',
controller: 'mainController'
})
.state('login', {
url: '/login',
templateUrl: 'views/login/',
controller: 'loginController'
});
$locationProvider.html5Mode(true);
}
And here are the routes on the back end that would be relevant:
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', function(req,res){
res.sendFile(path.join(__dirname, 'public/', 'index.html'));
});
The only other routing I have is for my API, which use the constant apiRouter and inject /api/ into those routes.
Now, the issue comes when I try to access a route directly from the browser. I've I nagivate to localhost:8080/login I get the error Cannot GET /login, but using a ui-sref on my index page, it seems to work just fine.
Any ideas?
So I figured out what my issue was.
After my root path GET statement in my server file, I need to add the following code:
app.get('*', function(req,res){
res.sendFile(path.join(__dirname, 'public/', 'index.html'));
});
What was happening, is that Express was trying to find a /login route that I had defined. The problem is, that route needs to be handled by Angular, and not Express, so the '*' is a catch-all for any route that Express doesn't know how to explicitly handle, it then hands off that responsibility to the index.html file.

Routing working locally, not on Heroku servers

My AngularJS application won't route to /login when accessing /login directly. It will route to /login if I first access / then route from / to /login.
It is working on my local environment but not with Heroku servers. Are there some settings I have to configure on the Heroku server?
I am using angular-ui-router to route to different states throughout my application.
My app.js config snippet looks like this:
angular.module('app', [
angularUiRouter
])
.config(($stateProvider) => {
"ngInject";
$stateProvider
.state("home", {
url: "/",
template: "<home></home>"
})
.state("login", {
url: "/login",
template: "<login></login>"
});
})
Answering my own question.
Since the setup on this application is using MEAN stack, we have to also add routing from server side to client.
For all templates in AngularJS there has to be a routing from Express to AngularJS to index.html
for all templates and routings created in angular we need to get the request and send index.html in response
when we use angularjs stateProvider to route in Angularjs.., we have to also add routing to /dist/index.html from server side.
$locationProvider.html5Mode(true).hashPrefix('!'); has to be defined in app.js on AngularJS side
A snippet of my Server.js file:
var express = require('express');
var app = express();
app.use(express.static(__dirname + '/dist'));
app.get('/', function(req, res){
res.sendFile(__dirname + '/dist/index.html');
});
app.get('/login', function(req, res){
res.sendFile(__dirname + '/dist/index.html');
});

Angular URL routing issue

So I needed to change my URL so that google analytics could track it. Google analytics wouldn't accept it with the "/#/" (hash) in the link. That said, I used Angular's locationProvider and revised my app routing with:
(function() {
'use strict';
angular
.module('mbapp')
.config(routerConfig);
/** #ngInject */
function routerConfig($stateProvider, $urlRouterProvider, $locationProvider) {
$stateProvider
.state('home', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainController',
controllerAs: 'main'
});
$stateProvider
.state('steps', {
url: '/steps',
templateUrl: 'app/steps/steps.html',
controller: 'StepsController',
controllerAs: 'steps'
});
// use the HTML5 History API
$locationProvider.html5Mode(true);
$urlRouterProvider.otherwise('/');
}
})();
My URL is fine and changes it to http://website.com/steps rather than http://website.com/#/steps. However, now, if a user refreshes (f5) the link it then throw a 404 error and not sure why. Additionally, it seems that somehow this gets injected as the URL when the refresh is called "http://website/steps#/steps".
Any ideas on this?
Thanks much.
The problem is probably on the server side. You have to configure your server so it responds to every request with your html file. For example in express:
var app = require('express')();
app.configure(function(){
// static - all our js, css, images, etc go into the assets path
app.use('/assets', express.static('/assets'));
app.get('/api/users/:id', function(req, res){
// return data for user....
});
// This route deals enables HTML5Mode by forwarding missing files to the index.html
app.all('/*', function(req, res) {
res.sendfile('index.html');
});
});
When you reload the page, the request goes to the server side of your application, and it tries to resolve the url but it probably can't, because those routes only exists on the client side of your application.
Also it is a good idea to prefix every server side route with an /api route prefix, so you can easily distinguish between client side and server side routes.

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

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?

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');
});

Resources