Meteor Iron Router default route notFound - meteor

I have created a meteor app using iron router and the default router looks like this
Router.route('/', {
this.render('Ft');
});
However when i load http://localhost:3000/ i constantly get no found and thus the template Ft is not being loaded.
How can i define the default route in my application?.
Secondly,i have a controller function which looks like this
home: function() {
this.render('Ft');
},
How can i define a controller function that can handle / which is lie the index controller function?.

You need to pass an object:
Router.route('/',{
name: 'home',
template: 'Ft',
controller(){
/* your controller function. You don't need to render in that controller
if you define the template above */
}
});

Related

How to load different views according to entered rout in FlowRouter using angular-meteor in Meteor

I'm using FlowRouter for client side routing and I want to load different views and angular module according to the entered rout. For example:
FlowRouter.route('/u/:id', {
name: 'Messages.show',
action(params, queryParams) {
check(params.id, String);
hash = params.id;
require('./u.html');
}
});
FlowRouter.route('/m/:id', {
name: 'Messages.show',
action(params, queryParams) {
check(params.id, String);
hash = params.id;
require('./m.html');
}
});
I want to load completely different templates and files in for each route

Async load routes data and build route instruction for Angular 2

I try to build dynamically routes from angular2 (fetch route config from server), after that I parse it and generate instruction for component route (I have parent routes config and child into different components, because I don`t know how define route for child component into one main.app.ts file).
The problem is when app started and try to create routes config and routeGenerator is not build routes yet (async delay) cant parse routes data (because async delay, so routesData undefined now) and app is crashig. I dont know what to do with this. Looking for lifecycle hood (some like - #Angular2BeforeAppStarted ) but found nothing.
import {Component, Input, OnChanges} from 'angular2/core';
import {RouteConfig, RouterOutlet, ROUTER_DIRECTIVES, Router} from 'angular2/router';
/* ------- !Angular 2 native components ---------*/
import {routeGenInstance} from '../../config/routes/patient_routes';
protected const BUILT_MODULE_PATH: string = '/built/modules/patients/';
#Component({
template: `
<router-outlet ></router-outlet>
`,
directives: [RouterOutlet, ROUTER_DIRECTIVES]
})
#RouteConfig(routeGenInstance.getRouteDefinitions())
export class PatientsComponent {
#Input();
constructor() {}
}
Also i try to update routes in the same way (but app is crashed immediately because my Navigation link in navigation component is not have some correct link way)
import {RouteConfig, RouterOutlet, ROUTER_DIRECTIVES, Router} from 'angular2/router';
constructor(
private router: Router
) {
router.config([
routeGenInstance.getRoutesDefinition()
])
}
my route definitions use Async loader so they are correct and work whithout async delay. I don`t know how to make angular wait for my routes definitions and thet start to run the app.
Please, help me. Thanks.
UPD:
#Thierry many thanks for your help again. You are awesome my friend and mentor. One last question (last). Can you tell me how I can define routeConfig into one app file with child subrouting definition?
Its mean. I have main level routes into app files
{
path: '/',
name: 'Dashboard',
component: DashboardComponent,
useAsDefault: true
},
{
path: '/patients/...',
name: 'Patients',
component: PatientsComponent
},
and patient sub routes into patientsComponent (#RouteConfig)
{
path: '/', // root is appRoot/patients/...
name: 'PatientsList', component...},
{
"name": "Chart",
"path": "/chart/:id", component...
},
How to define this route config only into one app.file ? (How to configure route with sub routing in one file)?
An option could be to get your configuration before bootstrapping your application.
var injector = Injector.resolveAndCreate([HTTP_PROVIDERS]);
var http = injector.get(Http);
http.get('routes.json').map(res => res.json())
.subscribe(data => {
bootstrap(AppComponent, [
HTTP_PROVIDERS
provide('routesConfig', { useValue: data })
]);
});
Then you can have access the routes configuration by dependency injection and in a synchronous way:
#Component({
(...)
})
export class AppComponent {
constructor(#Inject('routesConfig') private routesConfig, private router:Router) {
// Configure here your routes
}
}
These two questions could help you:
How to bootstrap an Angular 2 application asynchronously
angular2 bootstrap with data from ajax call(s)
Edit
You can leverage the Observable.forkJoin method to load your route configuration from different requests:
var injector = Injector.resolveAndCreate([HTTP_PROVIDERS]);
var http = injector.get(Http);
Observable.forkJoin([
http.get('mainRoutes.json'),
http.get('childRoutes.json')
])
.map(responses => {
return {
main: res[0].json(),
children: res[1].json()
};
})
.subscribe(data => {
bootstrap(AppComponent, [
HTTP_PROVIDERS
provide('routesConfig', { useValue: data })
]);
});
Edit1
I think that you could try something like that:
[
{
path: '/patients/...',
name: 'Patients',
component: PatientsComponent,
childRoutes: [
{
path: '/', // root is appRoot/patients/...
name: 'PatientsList', component...
},
(...)
]
}
]
But you need to split the content to get different elements according to the hints you want to handle:
one for the root:
[
{
path: '/patients/...',
name: 'Patients',
component: PatientsComponent
}
]
several for children. For example for patients:
[
{
path: '/', // root is appRoot/patients/...
name: 'PatientsList', component...
},
(...)
]
In the new router (>= RC.3) https://angular.io/docs/js/latest/api/router/index/Router-class.html#!#resetConfig-anchor resetConfig can be used
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'simple', component: SimpleCmp },
{ path: 'user/:name', component: UserCmp }
] }
]);
See also https://github.com/angular/angular/issues/9472#issuecomment-229230093
You can load components asynchronously by providing a SystemJsComponentResolver.
Right now, you can load routes asynchronously and imperatively update the configuration using resetConfig.
Once AppModules are in master, we will utilize those to implement async loading of subconfigs.
https://github.com/angular/angular/issues/11437#issuecomment-245995186 provides an RC.6 Plunker
Check this:
DynamicalAsyncRouter
https://github.com/Longfld/DynamicalAsyncRouter
Using Observables/Promises to provide route translations is not a reliable solution, hence the Angular router expects Route[] or Routes, but an HTTP request can only return an Observable/Promise.
The Angular app gets initialized, but the retrieval process of route translations still goes on using Observables/Promises.
As Thierry Templier said, to get your configuration before bootstrapping your application would solve the problem.
Also, check the #ngx-i18n-router/core on github.

ASP.NET 5 + Angular 2 routing (template page not REloading)

Angular 2 beta uses html5 routing by default.
However, when you go to a component and the route changes (eg http://localhost:5000/aboutus) and you reload/refresh the page, nothing is loaded.
The issue has been raised in this post also.
Most of the answers say that if we are going to pursue HTML5 routing in angular 2, then this issue of routing should be taken care of in server-side. More discussion here.
I am not sure how to handle this issue using the asp.net server environment.
Any angular 2 devs out there who also uses asp.net and encounters this issue?
PS. I'm using ASP.NET 5. My Angular 2 routes are using MVC routes.
The problem you're seeing has to do with the difference between Angular routing on the client and MVC server-side routing. You are actually getting a 404 Page Not Found error because the server does not have a Controller and Action for that route. I suspect you are not handling errors which is why it appears as if nothing happens.
When you reload http://localhost:5000/aboutus or if you were to try to link to that URL directly from a shortcut or by typing it into the address bar (deep linking), it sends a request to the server. ASP.NET MVC will try to resolve that route and in your case it will try to load the aboutusController and run the Index action. Of course, that's not what you want, because your aboutus route is an Angular component.
What you should do is create a way for the ASP.NET MVC router to pass URLs that should be resolved by Angular back to the client.
In your Startup.cs file, in the Configure() method, add an "spa-fallback" route to the existing routes:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
// when the user types in a link handled by client side routing to the address bar
// or refreshes the page, that triggers the server routing. The server should pass
// that onto the client, so Angular can handle the route
routes.MapRoute(
name: "spa-fallback",
template: "{*url}",
defaults: new { controller = "Home", action = "Index" }
);
});
By creating a catch-all route that points to the Controller and View that ultimately loads your Angular app, this will allow URLs that the server does not handle to be passed onto the client for proper routing.
In your Startup.cs add this to the Configure method. This must be before other app statements.
app.Use(async (context, next) => {
await next();
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value)) {
context.Request.Path = "/index.html"; // Put your Angular root page here
await next();
}
});
My favorite solution is to add the following code to Global.asax.cs which very smoothly and reliably takes care of the issue:
private const string RootUrl = "~/Home/Index";
// You can replace "~Home/Index" with whatever holds your app selector (<my-app></my-app>)
// such as RootUrl="index.html" or any controller action or browsable route
protected void Application_BeginRequest(Object sender, EventArgs e)
{
// Gets incoming request path
var path = Request.Url.AbsolutePath;
// To allow access to api via url during testing (if you're using api controllers) - you may want to remove this in production unless you wish to grant direct access to api calls from client...
var isApi = path.StartsWith("/api", StringComparison.InvariantCultureIgnoreCase);
// To allow access to my .net MVCController for login
var isAccount = path.StartsWith("/account", StringComparison.InvariantCultureIgnoreCase);
if (isApi || isAccount)
{
return;
}
// Redirects to the RootUrl you specified above if the server can't find anything else
if (!System.IO.File.Exists(Context.Server.MapPath(path)))
Context.RewritePath(RootUrl);
}
You need use this routing in ASP.NET MVC
app.UseMvc(routes =>
{
routes.MapRoute("Default", "{*url}", new { #controller = "App", #action = "Index" });
});
Then you need set up SystemJS with basePath options
The feature you're looking for is URL rewrite. There are two possible ways to handle it. The classic way is to let IIS do the work, as described here:
https://stackoverflow.com/a/25955654/3207433
If you don't want to depend on IIS, you can instead handle this in the ASP.NET 5 middleware, as shown in my answer here:
https://stackoverflow.com/a/34882405/3207433
I'm not having any luck getting
routes.MapRoute("Default", "{*url}",
new { #controller = "App", #action = "RedirectIndex" });
to work. I still get a 404 with any client side route.
Update:
Figured out why the catch-all route wasn't working: I had an attribute route defined ([Route("api/RedirectIndex")]) and while the plain route can be directly accessed with the fallback route it didn't fire. Removing the attribute route made it work.
Another solution that seems to work just as easy as the catch-all route handler is to just create a custom handler that fires at the end of the middleware pipeline in Configure():
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
//handle client side routes
app.Run( async (context) =>
{
context.Response.ContentType = "text/html";
await context.Response.SendFileAsync(Path.Combine(env.WebRootPath,"index.html"));
});
This basically ends up being the catch-all route that simply sends index.html out over the existing URL request if there was no other handler that picked up the request.
This works nicely even in combination with IIS Rewrite rules (in which case the above just won't ever get fired.
Wrote up a blog post on this topic:
Handling HTML5 Client Route Fallbacks in ASP.NET Core
Here are two more options for solving this problem. You can either add the hash location strategy to your app module.
import { LocationStrategy, HashLocationStrategy } from '#angular/common';
#NgModule({
imports: [.... ],
declarations: [...],
bootstrap: [AppComponent],
providers: [
{
provide: LocationStrategy,
useClass: HashLocationStrategy
}
]
})
export class AppModule { }
This option will only work for the parts of your Angular2 app that live on the Home ASP Controller
Your second option is to add routes to your ASP Controller that match your Angular 2 app routes and return the "Index" View
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[ActionName("Angular-Route1")]
public IActionResult AngularRoute1()
{
return View("Index");
}
public IActionResult Route2()
{
return View("Index");
}
}
Did you use:
directives: [RouterOutlet, RouterLink] in the component.
apply the #ZOXEXIVO's solution then, in your _Layout.cshtml add this:
<head>
<base href="/"/>
.....
</had>
You can use both the routing
when you call Home/Index from angular routing.
write
Home/Index.cshtml
<my-app></my-app>
app.routing.ts
const routes: Routes = [
{ path: '', redirectTo: '/Home/Index', pathMatch: 'full' },
{ path: 'Home/Index', component: DashboardComponent }
]
So When URL will be Home/Index
will load the component of active url so it will load dashboard component.
The above selected solution did not work for me I also got 404 after following all the comments to the T. I am using an angular5 app in an MVC5 app. I use the default index landing page as the start for the angular5. My angular app is in a folder named mvcroot/ClientApp/ but on ng build it puts the distributed files in mvcroot/Dist/ by altering one setting in the .angular-cli.json file with "outDir": "../Dist"
This solution did work though.
This way only routes in the Dist directory get the fall over. Now you can hit refresh every time and exact route for the angular5 app reloads while staying on the correct component. Be sure to put the catch all first. On a side note, if using a token auth in your angular5, save the token to window.localStorage (or some other mechanism outside your angular5 app) as hitting refresh will wipe out all memory where you you maybe storing your token in a global variable. This keeps the user from having to login again if they refresh.
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Catch All",
"dist/{*url}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Route using wrong controller?

I have 2 controllers (iron-router), one for access bits (login etc.) and one for the logged in area. But for some reason one of my routes is choosing to use the wrong controller, even though I'm explicitly stating which one to use. Here is the code:
// Controllers
AccessController = RouteController.extend({
layoutTemplate: 'AccessMaster',
onBeforeAction: function () {
if (Meteor.user()) { // If user is logged in then take them to the Dashboard
this.redirect('/app/dashboard');
} else {
this.next();
}
}
});
DashboardController = RouteController.extend({
layoutTemplate: 'DashboardMaster',
onBeforeAction: function () {
if (!Meteor.user()) { // If user is not logged in then take them to the login
this.redirect('/app/login');
} else {
this.next();
}
}
});
// Routes
Router.route("/app/signup", {
name: 'Signup',
controller: 'AccessController'
});
Router.route("/app/login", {
name: 'Login',
controller: 'AccessController'
});
Router.route("/app/account", {
name: 'Account',
controller: 'DashboardController',
loadingTemplate: 'Loading',
action: function () {
this.render('Account');
}
});
Router.route("/app/dashboard", {
name: 'Dashboard',
controller: 'DashboardController',
loadingTemplate: 'Loading',
waitOn: function () {
…
},
action: function () {
this.render('Dashboard', {
data: {
…
}
});
}
});
When I visit app/account I'm redirected to app/dashboard, as directed in the AccessController. Why is the app/account route using the wrong controller logic?
Edit: Oddly, if I remove the controller declaration in the offending route (controller: 'DashboardController') then the template loads fine. So it only uses the wrong controller when I ask it to us a controller.
I must be missing something but that's awfully odd.
I think that your problem comes from the fact that you are using Meteor.user() in both controllers, which is the actual user document. And like any other collection it may not be immediately ready when the application starts.
If you add a console.log(Meteor.user()) in your controllers, you will see that it is first briefly undefined before returning the user document.
So the route is using the right controller but Meteor.user() is undefined so you are redirected to /app/login where Meteor.user() (probably ready now) returns the documents so you get redirected to /app/dashboard.
To prevent such behavior I use Meteor.userId() which is always available no matter what. And I only use Meteor.user() when I have first tested that Meteor.userId() returned something and if I need more information about the user.

Iron Router controller inheritance, accessing a ApplicationController outside of file

I want to implement an inheritance scheme for my routes. I want all my routes to inherit from my ApplicationController
ApplicationController = RouteController.extend({
subscriptions: function() {
this.user = Meteor.subscribe('userEarnings', Meteor.userId());
},
data: function() {
return {
currentUser: Users.findOne(),
userReady: this.user.ready()
};
}
});
Which makes sure all custom fields are subscribed to on my user collection. I have the above stored in a file client/lib/routes/main.js. I want to be able to extend my other controllers in client/lib/routes/*.js with ApplicationController, like this
historyController = ApplicationController.extend({
layoutTemplate: 'dashboardLayout',
subscriptions: function() {
this.subs = Meteor.subscribe("userPurchaseHistory", Meteor.userId());
},
data: function() {
console.log(this.user.ready());
return {
purchases: Purchases.find(),
ready: this.subs.ready()
};
}
});
Router.route('history', {
path: '/history',
loginRequired: 'entrySignIn',
controller: historyController
});
but currently, I get
Uncaught ReferenceError: ApplicationController is not defined
How can I access ApplicationController outside of its file? I thought that by not using var, the variables would be globally accessable.
This is a load order issue. As per the docs:
File Load Order
All files that match main.* are moved after everything else, preserving their order.
...
Within a directory, files are loaded in alphabetical order by filename.
Try putting the ApplicationController definition in client/lib/routes/application-controller/application-controller.js or something like that to make absolutely sure it runs before any of the other controllers try to inherit from it. Provided it's not called main.js, having it in a subdirectory will ensure it's added before the stuff in the parent directory and obviate the potential alphabetical order problem.

Resources