AngularJS and Laravel 4 routing conflict in HTML5 mode - html

I would like to remove the # hash from URLs using Angularjs' $locationProvider.html5Mode(true).
Example: The address bar displays http://localhost/shop instead of http://localhost/#/shop.
Everything works well untill I refresh a page. If i refresh, the following Laravel Route (defined in routes.php) is accesed
Route::resource('shop', 'ShoppingController')
not the AngularJS Route (defined in app.js)
$routeProvider.when('/shop', {
templateUrl: 'templates/shop.html',
controller: 'ShoppingController'
});
My Code:
routes.php (Laravel Routes)
Route::get('/', function() {
return View::make('index');
});
Route::resource('shop', 'ShoppingController');
app.js (AngularJS Routes)
var app = angular.module('shoppingApp',['ngRoute','SharedServices']);
app.config(function($routeProvider, $locationProvider) {
$routeProvider.when('/shop', {
templateUrl: 'templates/shop.html',
controller: 'ShoppingController'
});
$routeProvider.otherwise({ redirectTo: '/' });
$locationProvider.html5Mode(true);
});
My directory structure:
Project
/app
/...
/views
-index.php (single page application file)
-routes.php (Laravel routes)
/public
/...
/js
-angular.js
-app.js
-index.php (Laravel index file)
Tried Solutions:
Rewrite the htaccess file so that all requests are redirected to index.php (the single page application file, from where AngularJS would take over the routing). Problem: In this way the Laravel route (Route::resource('shop', 'ShoppingController'); - necessary for interaction with the database) becomes inaccessible to the AngularJS $http service:
app.js
app.controller("ShoppingController", function($scope, $http) {
$http.get('/shop', { cache: true}).
success(function(data, status) {
$scope.items = data
}).
error(function(data, status) {
console.log('Status: ' + status);
});
});
Question:
How can I solve the routing problem, so that the AngularJS route, not the Laravel Route gets accessed if I refresh localhost/shop?

From what I read, it seems like Laravel is reading the modified route when you refresh the page. In this case, you should make Laravel continue to make the original view even if it would otherwise be a 404 redirect.
Try adding the following somewhere on the Laravel side (Ex. routes.php)
App::missing(function($exception)
{
return View::make('index');
});
Note: You might want to have AngularJS's routing use .otherwise to handle pages that are not found.

A better solution is to redirect this way:
'Redirect::to('/#/' . Request::path())'
When you refresh or go to the URI directly:
'Request::path()': returns the requested URI i.e.
('/shop/categories/electronics');
AngularJS in 'html5Mode' still responds to the '#/' prefix;
If angular detects the prefix when in HTML5 mode it will remove the prefix for you.
Final solution:
App::missing(function($exception) {
return Redirect::to('/#/' . Request::path());
});

If you are using Laravel 5 then go to app/Exception/Handler.php and place the code below:
public function render($request, Exception $e)
{
if($e instanceof NotFoundHttpException)
{
return Redirect::to('/#/' . Request::path());
}
return parent::render($request, $e);
}

If you wana have more than one single page application running in html5mode or just have another use for App::missing inside a Laravel app you migh use a rewrite rule like this:
#Redirect base url of AngularJS app in html5mode
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} ^/path/.+$
RewriteRule ^(path)/(.*) /path/#/$2 [R=301,L,NE]

I have another solution which I found quite useful. Rather than just making home page view, I pass in the URI to the home page, which will get checked by a controller and redirect accordingly (the Angular way). This means that if you are on myapp.com/about and you refresh, instead of taking you home, it takes you back to the page you were currently on.
routes.php: Notice that I have a URI wildcard that I pass in as an argument to the callback function, then as a variable to the view.
// Note that this must be on the bottom of your routes file because
// if you have any registered route with a similar pattern
// it will get caught by this route and never reach any registered routes you might have
Route::get('{slug}', function($slug){
return View::make('index', compact('slug'));
});
// These routes will never get hit, so move them above Route::get('{slug}')
Route::get('about', function(){...});
Route::get('contact', function(){...});
index.blade.php:
<html ng-app"myApp">
<head>
...
</head>
<body>
<!--Check if there is a variable named $slug that is set-->
<!--If it is, set hidden input with ng-model-->
#if(isset($slug))
<input type="hidden" value="{{slug}}" ng-model="slug" ng-controller="RedirectController">
#endif
<div ng-view></div>
</body>
</html>
app.js
angular.module('myApp', [])
.controller('RedirectController', ['$location', '$scope', function ($location, $scope) {
// If slug is set, redirect to that slug
if ($scope.slug) {
$location.path('/' + $scope.slug);
}
}]);

For Laravel 4.x or 5.x i use this simple and nice trick and there is no need to change .htaccess file. this trick is very simple and works for me. it doesn't redirect URL and user will stay on same URL when refresh page and page will refresh:
Route::get('dashboard/{all?}', function(){
return view('main.index');
});
here my dashboard app is my SPA. this way we can resolve 404 errors pages too.

Related

Express server loads html page with/without css/js when last route character is without/with '/', respectively

The following is affecting my second html page model.html:
If my route address is with a '/ at the end (typed on browser addr field) like so: http://localhost:3002/home/model/ then the correct html page is loaded, but no css/js is loaded.
If my route address is without a '/ at the end like so: http://localhost:3002/home/model then the correct html page is loaded, and css/js is loaded.
Without '/' at the end, css/js loads fine using statics/css/style.css and js/dynamicData.js
The weird part is that when '/' is at the end, I can use ../statics/css/style.css to load the css and "../js/chartData.js" to load js - but that means the one without '/' now longer loads the css/js.
My folder structure:
js - has other js scripts
node_modules
statics
css - has style.css
image - has images
index.html
model.html
index.js - init express server
pc_server.js - express server
Express code (pc_server.js)
Middleware setup?:
process.chdir(__dirname);
// base = '/home'
app.use(base, express.static(__dirname));
Routes:
const INDEX_PAGE = '/';
const MODEL_PAGE = '/home/model';
function setupRoutes(app) {
const BASE = app.locals.base;
app.get(INDEX_PAGE, redirectHome(app));
// BASE = '/home'
app.get(BASE, toHomePage(app));
app.get(MODEL_PAGE, toModelPage(app));
Routes functions defined:
function redirectHome(app) {
return errorWrap(async function(req, res) {
try {
res.redirect(app.locals.base);
}
catch (err) {
console.error(err);
}
});
}
function toHomePage(app) {
return errorWrap(async function(req, res) {
try {
res.sendFile(path.join(__dirname+'/statics/index.html'));
}
catch (err) {
console.error(err);
}
});
}
function toModelPage(app) {
return errorWrap(async function(req, res) {
try {
res.sendFile(path.join(__dirname+'/statics/model.html'));
}
catch (err) {
console.error(err);
}
});
}
The goal is to load the same page with css/js with either http://localhost:3002/home/model/ or http://localhost:3002/home/model
Additional: Why is it that when I type http://localhost:3002/home I get http://localhost:3002/home/ automatically on my browser addr field?
the problem
it probably occurs due to relative links in your site.
when home/model is used - relative css/style.css link will lead to home/css/style.css
when home/model/ is used, the same link will lead to home/model/css/style.css
the solution:
the easiest way to solve it is changing your link tag to:
<link rel="stylesheet" type="text/css" href="../../home/statics/css/style.css">
this link goes to the root address, and then enters your path Independently from the user's path.
why it's working?
the ../../ prefix tell the browser to go two levels up.
the browser consider the "home/model/" as a visit inside a model folder inside home folder. two levels upward lead the browser to the root level, where it has a clean start.
when the user visits "home/model", it considered as a file inside the home folder. one level upward is the root level, and the second ../ does nothing.
after achieving the root level - the browser entering "home/statics/css/style.css" and find the right file in both cases :)

Removing '#' from URL using htaccess in HTML web page

I am trying to remove the # in the following URL: (www.example.com/#section1). How could I do this using the htaccess file. I am sure this could be done using regular expression, but I am not sure how I would do this.
This is what I have written within the htaccess file RewriteRule ^[#].
Thanks for your help!
Hashes (#) are not send to the server, so you can't manipulate them on the server.
If you really need to remove them, you would have to use JavaScript on each page.
// Wait for the page to load, and call 'removeHash'.
document.addEventListener("DOMContentLoaded", removeHash);
document.addEventListener("Load", removeHash);
function removeHash() {
// If there is no hash, don't do anything.
if (!location.hash) return;
// http://<domain></pathname>?<search><#hash>
// Build an URL for the page, sans the domain and hash
var url = location.pathname;
if (location.search) {
// Include the query string, if any
url += '?' + location.search;
}
// Replace the loaded url with the built url, without reloading the page.
history.replaceState('', document.title, url);
}

Setting base url with AngularJs not working

I am using $http.get with a relative url like this:
$http.get('/api/product')
.then(function (res) { });
The resultant call is:
GET http://localhost/api/product 404 (Not Found)
Now the problem is my site is running in IIS under a virtual directory, the structure is like this:
Sites
- Default Web Site
- Website (Virtual Directory)
- Services (Virtual Directory)
So the URL I actually want AngularJs to call is:
GET http://localhost/website/api/product
I have tried setting the BASE element like this:
<base href="Website/">
(And using http://localhost/website/)
But nothing seems to correct the URL?
What can I do? I would still like to use the relative URL with angularjs.
Try this: $http.get('api/product')
.then(function (res) { });
Change from $http.get('/api/product').then(function (res) { }); to $http.get('/website/api/product').then(function (res) { });

Html5 mode Angularjs Express URL rewrite returns index page for ALL requests

I've been using similar questions to try and find the solution to the problem I'm having. I understand that in order to use html5Mode with angularjs I need to tell the server how to handle a direct visit to a page.
I have the issue where clicking on the link within the app renders the page fine but a direct visit does not display offers.
E.g.
http://localhost:3001/offers/television/
should call in routes.js
app.get('/offers', offers.all);
and it does when the link
televisions
is clicked
When I directly visit it however it looks like my angular service is returning the index page as resources...!
//Offers service used for offers REST endpoint
angular.module('mean.offers').factory("Offers", ['$resource','$routeParams',function($resource,$routeParams) {
return $resource('/offers/:offerId',
{offerId: '#_id'},
{
search: {'method': 'GET', params: {}, isArray:true}
});
}]);
I've also got base(href="/") in my index.jade head
angular config.js
//Setting up route
window.app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/offers/:type/',{
controller: 'OffersController',
templateUrl: 'views/offers/list.html'
}).
when('/', {
templateUrl: 'views/index2.html'
}).
otherwise({
redirectTo: '/'
});
}
]);
//Setting HTML5 Location Mode
window.app.config(['$locationProvider',
function($locationProvider) {
$locationProvider.hashPrefix("!");
$locationProvider.html5Mode(true)
}
]);
express routes.js
//Offer Routes
var offers = require('../app/controllers/offers');
app.get('/offers', offers.all);
app.get('/offers/:offerId', offers.show);
//Home route
var index = require('../app/controllers/index');
app.get('/', index.render);
express.js
app.configure(function() {
// app.use('/', express.static(__dirname + '/'));
//cookieParser should be above session
app.use(express.cookieParser());
//bodyParser should be above methodOverride
app.use(express.bodyParser());
app.use(express.methodOverride());
//routes should be at the last
app.use(app.router);
app.get('/*', function(req, res) {
res.render('index');
});
...
Why is it not returning offers even though it should hit the /offers route in express routes.js? Or am I doing something odd?
Thanks!
As you mentioned in the comments of the question, "app.get('/offers', offers.all); will handle /offers/:offerId". This means that going directly to http://localhost:3001/offers/television/ will be handled by your offers.all function (not shown in post), not the '/*' handler that returns the index.
To fix this you have options.
Check the route to see if it is an AJAX request or not. If it is,
return your data, if it is not, return the index.
Put your API behind a path (like /api) then all of your API requests will go to
/api/offers/:offId to get data. This frees up /offers/:offerId
to be handled by '/*', returning the index
Edit:
I see the confusion, app.router (Node.js / Express.js - How does app.router work?). In a nutshell, app.use(app.router); tells express which order to run the routes as a whole. The order you provide the routes in matter after that point. From your code (again, not showing all of it) you only really define 1 route, app.get('/*', function(req, res) { res.render('index'); });. You have the separate route files, but no where in what you have posted do you includes those scripts. They are not automatically included by default.

angular routing something weird happening

I am still learning angularjs so maybe there's something stupid I am not understanding but I have a really strange behaviour when using routing.
In my application I use the following code to define my routes:
var app = angular.module('app', []);
app.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider.
when('/pneumatici/:chunka', {}).
when('/pneumatici/:chunka/:chunkb', {});
$locationProvider.html5Mode(true);
}]);
And in a controller I manage them this way:
app.controller('appCtrl', ['$scope', '$route', '$routeParams', '$location', function ($scope, $route, $routeParams, $location) {
$scope.$on('$routeChangeSuccess', function (current,previous) {
if (!($location.path().indexOf('/pneumatici') === -1)) {
$scope.chunka = $route.current.params.chunka; $scope.chunkb = $route.current.params.chunkb;
/** do my stuff with chunka and chunkb **/
} else {
window.location.href = $location.path();
}
});
I have no ngView, no template, nothing.
It works like a charm.
Please note the line where I actually force a page load in case the url is not intended to be managed by the controller appCtrl.
I was forced to do that because once I define my route to catch '$routeChangeSuccess' all links in the page when clicked are catched by angular and no page load occurs even if the link doesn't have the format defined with 'when'.
I would have like to do it with 'otherwise' but I could not understand how to, if doable.
Now the problem.
In the page of course I have links like just '/privacy.html', if I click them the page load is correctly triggered and I do see '/privacy.html' but unfortunately once there if I click the back button I can see the url of the browser changing to (let's say) /pneumatici/foo/bar but no page load is triggered.
Please note in the privacy.html page I have no angular routing defined, there's no .config no .when; there's an anagular app defined, with a controller, but no injection of '$routeProvider' anywhere, no definition of any route.
What is happening? What I am doing wrong?
Thanks for any help!
Update.
I found a viable solution adding:
angular.element("a").prop("target", "_self");
Angular routing is ignored for all 'a' elements with 'target' set to "_self", didn't know that.
Still if I look at this strategy as a whole doesn't sound very elegant to me and I would love to improve it. What I don't like is since I am defining the route in .config I should be able to tell angular to skip any url which do not match the format/path I defined there.
But I don't know if that is doable or not, does anyone know out there?
By turning on html5mode your app should be acting like it should intercept everything on the site by default (from '/'.)
From that perspective, it seems like $location.path() should work in your explicit override, but it isn't really correct ($location.url() would be) and the browser already has the correct URL, so maybe you can't force a reload with location.href = location.href in your specific browser.
Rather than going down that path, I would do the following to make it DRY:
If you add a base href:
<base href="/pneumatici/"></base>
and replace /pneumatici/ with / in your when clause(s):
$routeProvider.
when('/:chunka', {}).
when('/:chunka/:chunkb', {});
then you should just need this:
$scope.$on('$routeChangeSuccess', function (current,previous) {
$scope.chunka = $route.current.params.chunka;
$scope.chunkb = $route.current.params.chunkb;
/** do my stuff with chunka and chunkb **/
});
I think you should let Angular manage all your routes like this:
var app = angular.module('app', []).config(function($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
$routeProvider
.when('/',
{
controller: 'HomeController',
templateUrl: '/partials/home.html'
})
.when('/about',
{
controller: 'AboutController',
templateUrl: '/partials/about.html'
})
.when('/privacy',
{
controller: 'PrivacyController',
templateUrl: '/partials/privacy.html'
})
.when('/404',
{
controller: 'NotFoundController',
templateUrl: '/partials/404.html',
})
.otherwise({
redirectTo: '/404'
});
});
Notice the otherwise above. That tells angular to load the 404 page when a route is not recognized.