Simple question.
I'm building an Express web application with two views/routes (controlled by Angular):
localhost:3000/#/join
localhost:3000/#/find
I want the initial "localhost:3000" to forward to "localhost:3000/#/join", but currently, the page only loads the generic static content and does not include the unique html partial content associated with the view.
I'm using the following code.
require('./app/routes.js')(app);
app.all('*', function(req, res){
res.redirect('/#/join');
});
The forwarding works correctly for all url (e.g. localhost:3000/blah, localhost:3000/blah2, etc.) -- except for the initial localhost:3000.
Any suggestions?
Figured out the answer. I just need to include an "otherwise" statement at the end of my Angular routeProvider.
var app = angular.module('meanMapApp', ['addCtrl', 'queryCtrl','ngRoute'])
.config(function($routeProvider){
$routeProvider.when('/join', {
controller: 'addCtrl',
templateUrl: 'partials/addForm.html',
}).when('/find', {
controller: 'queryCtrl',
templateUrl: 'partials/queryForm.html',
}).otherwise({redirectTo:'/join'})
});
Related
I have a sample MVC6 single page app with one view in which I want to load 2 Angular partials using ngRoute. You can have a look at it at GitHub
There are 3 URLs in the app:
localhost - Index.cshtml
localhost/games - Index.cshtml with Angular's gamelist.html partial
localhost/games/2 - Index.cshtml with Angular's game.html partial
The routes config is the following:
MVC:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}");
routes.MapRoute("gamelist", "games", new { controller = "Home", action = "Index"});
routes.MapRoute("gameWithId", "games/2", new { controller = "Home", action = "Index" });
});
Angular:
myApp.config(['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
$routeProvider
.when('/games', {
templateUrl: 'partials/gameslist.html',
controller: 'GameController',
controllerAs: 'ctrl'
})
.when('/games/:gameId', {
templateUrl: 'partials/game.html',
controller: 'GameController',
controllerAs: 'ctrl'
});
$locationProvider.html5Mode(true);
}]);
It all works perfectly fine as long as I start the app from the home page '/' and then navigate to the partials using the links on the page. The problem is that the URL #3 (localhost/games/2) does not work if I start the app from it, by typing it in the address bar. The URL #2 (/games/) does work.
The reason why #3 does not work is that MVC removes '/games' part from the URL and what Angular gets is just '/2'. If you run the sample app, you will see that '$location.path = /2'. Of course Angular cannot map using that path and no partial is rendered. So my question is - how to make MVC return the full path to the client so that Angular can map it?
You can get it to work with HTML5 mode, you just need to ensure that every request maps back to your Index.cshtml view. At that point the AngularJS framework loads, client-side routing kicks in and evaluates the request URI and loads the appropriate controller and view.
We've done this with multiple Angular apps inside MVC with different .cshtml pages, though we use attribute routing with the wildcard character, e.g.
[Route("{*anything}")]
public ActionResult Index()
{
return View("Index");
}
The wildcard operator (*) tells the routing engine that the rest of the URI should be matched to the anything parameter.
I haven't had chance to get to grips with MVC6 yet but I think you can do something like this with the "new" version of attribute routing?
[HttpGet("{*anything:regex(^(.*)?$)}"]
public ActionResult Index()
{
return View("Index");
}
To make link #3 work from the browser's address bar, I turned off "html5Mode" in Angular and made links #-based.
kudos to this blog
I think it is a better solution.
His solution is rewriting the request that doesn't fit to any route and doesn't have any extension to the landing page of angular.
Here is the code.
public class Startup
{
public void Configure(IApplicationBuilder app, IApplicationEnvironment environment)
{
// Route all unknown requests to app root
app.Use(async (context, next) =>
{
await next();
// If there's no available file and the request doesn't contain an extension, we're probably trying to access a page.
// Rewrite request to use app root
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value))
{
context.Request.Path = "/app/index.html"; // Put your Angular root page here
await next();
}
});
// Serve wwwroot as root
app.UseFileServer();
// Serve /node_modules as a separate root (for packages that use other npm modules client side)
app.UseFileServer(new FileServerOptions()
{
// Set root of file server
FileProvider = new PhysicalFileProvider(Path.Combine(environment.ApplicationBasePath, "node_modules")),
// Only react to requests that match this path
RequestPath = "/node_modules",
// Don't expose file system
EnableDirectoryBrowsing = false
});
}
}
I'm following a tutorial on how to set up authentication with nodejs and passport. (http://scotch.io/tutorials/javascript/easy-node-authentication-setup-and-local)
The tutorial has me rendering templates with ejs and passing in flash data.
Instead of this, I'd like to use angularjs. The part I'm having trouble with is getting the flash data. I know how to use templates and send variables, but what in angular replaces the "req.flash('signupMessage')" in the below code?
This is the code the tutorial shows:
app.get('/signup', function(req, res) {
// render the page and pass in any flash data if it exists
res.render('signup.ejs', { message: req.flash('signupMessage') });
});
This is the code where I set up my route
// public/js/appRoutes.js
angular.module('appRoutes', []).config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$routeProvider
// show signup form
.when('/signup', {
templateUrl: 'views/signup.html',
controller: 'SignupController'
});
$locationProvider.html5Mode(true);
}]);
Here is the controller:
// public/js/controllers/SetupCtrl.js
angular.module('SignupCtrl', []).controller('SignupController', function($scope) {
$scope.tagline = 'TEST';
});
A similar question was answered here: What is the proper way to log in users using Angular & Express?
TLDR: the answer posted was to the following link, where the author describes that you need to keep all the passport stuff on the server side, and then allow the client side (angular stuff) to request information about the session.
http://vickev.com/#!/article/authentication-in-single-page-applications-node-js-passportjs-angularjs
Express.js routing of /question/ask
app.get('/question/ask', function (req, res){
console.log('index.js');
console.log('came to question/:id');
res.render('app');
});
The corresponding angularjs routing is:-
when('/ask', {
templateUrl: 'partials/askQuestion',
controller: 'xController'
}).
whereas it should be:-
when('/question/ask', {
templateUrl: 'partials/askQuestion',
controller: 'xController'
}).
I'm working in $locationProvider.html5Mode(true); mode.
Is there anyway i can get the later angularjs routing working. I'm using angularjs 1.1.5 version.
Edit:-
app.get('/*', function (req, res){
console.log('index.js');
console.log('came to question/:id');
res.render('app');
});
has the same problem, the angular route only routes the last /ask for /question/ask.
The issue for me is that I can only do 1 of the following :-
www.example.com/question/:qId
www.example.com/discussion/:aId
because the application will catch only 1 when('/:id', { as it does not include the previous /question/ or /discussion/
Well, if you have the same routes on Express and Angular, if the user types the url directly in the browser you will hit the Express route, but if the user is navigating within the application, then he will hit the Angular route.
Is this what you want ?
What some do is to have a different set of routes on the server for the REST API, and a catch all route to serve the application no matter what the user type as a URL, bringing the user to the home page when a server route is hit. Within the application of course navigation is handled by Angular routes. The problem is that you get no deep linking.
Some other apps have the same routes on both the server and the client, this way they can serve some contents no matter what.
Some will write involved route rewriting to make sure that you both get the application bootstrapping code AND the required URL, thus allowing deep linking.
Cheers
using angular version 1.2.0-rc.3 cures the problem.
change:
var myApp = angular.module('myApp', []);
to
var myApp = angular.module('myApp', ['ngRoute']);
And include:-
script(type='text/javascript', src='js/angular-route.js')
I am trying to pass an index value, for an array, into a route, so I can use the id to load a specific object into a detail view.
Controller that injects the index and changes path
location.path('/detail/'+index);
$routeProvider that handles the detail routing
.when('/detail/:index', {
controller: DetailViewCtrl, templateUrl: 'partials/detail'
});
Express script that handles the partial loading
app.get('/partials/:partial', function(req, res) {
return res.render('partials/' + req.params['partial']);
};
If I pass in an index of 5 then I expect the URL to look like "localhost:3000/detail/5", and I do get that in my browser but the server returns a 404 error where is was trying to look for some weird URL "localhost:3000/detail/partials/detail." I have no clue where the "detail " that is added before the partial is coming from.
It would be nice to know what is happening behind the scenes and how to fix the problem. How can I pass custom variables in the route and not have express freak out?
Prepend the templateUrl with /:
.when('/detail/:index', {
controller: DetailViewCtrl,
templateUrl: '/partials/detail'
});
or insert the base tag under the HTML head element as in:
<base href="/" />
I'm working on a Django app which makes heavy use of Angular in some pages, e.g. at domain.com/myAngularApp
Within the angular page I'm using Angular routing for navigating between different views/states within that page. However across the whole website there are navigation links which need to result in round trip requests to Django. However all the pages include the same compiled javascript file which includes the Angular route declarations.
So my question is: how to I get Angular to mange its own routes and get out of the way when the location is changed (primarily by clicking a link on the page) to a path that it hasn't explicitly been told to own, i.e. to different subdirectories off the domain.
My routing declaration looks something like:
myApp.config( function($locationProvider, $routeProvider) {
$locationProvider.html5Mode(true);
$routeProvider.when('/myAngularApp/', {
templateURL: 'template1.html'
});
$routeProvider.when('/myAngularApp/stuff', {
templateURL: 'template12.html'
});
$routeProvider.otherwise({redirectTo: <not sure what to do here...> });
})
I've tried something like:
$routeProvider.otherwise({redirectTo: function(a1,r,a3){ window.location.href = r }})
But this makes the page refresh endlessly on any non-matched route.
Leaving out the otherwise statement seems to make it impossible to leave a page with a non-matched route when accessed directly... don't really understand why?
It must be possible to do what I want no?
I think I may have found a solution. I'm doing a similar thing, a multi-page angular site that uses angular for some of it's pages. Here's what I'm doing
var app = angular.module('appname', ['ui.bootstrap', 'ui.autocomplete'])
.config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) {
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');
}])
.run(function($rootScope, $location) {
var redirected = false;
$rootScope.$on('$locationChangeStart', function(event, nextLocation, currentLocation) {
if(!redirected && $location.path() !== '/current-url') {
redirected = true;
event.preventDefault();
window.location = $location.path();
}
});
});
So what I have to work out next is how to pass in the current-url path. One way I'm thinking is to us ng-init to set that data in the view (I'm using express.js so I'd use Jade). Or possibly in the run function grab the initial path and test against that.
The event.preventDefault() is there to stop an extra item being added to the browsers history. Before I did that I had to hit back twice to get back to the angular page.
Note This hasn't been tested with IE8 yet. I'm going to do that now and see how it fairs.
Update Ok I just tested this in IE8 and I got stuck in a redirect loop. I've updated the code to have a simple variable to check if we've redirected. Seems to work. I'd love to know a prettier way.