Using named routes with Aurelia child routers
Asked Answered
T

1

9

I have 3 levels of nested routers, defined like this:

app.js

configureRouter(config, router) {
  this.router = router;
  config.title = 'EagleWeb';
  var step = new AuthorizeStep(this.core);
  config.addAuthorizeStep(step);
  config.map([
    { route: '', redirect: 'home' },
    { route: 'home',              name: 'home',               moduleId: 'home/home' },
    { route: 'users',             name: 'users',              moduleId: 'users/user-home' },
    { route: 'accounting',        name: 'accounting',         moduleId: 'accounting/accounting' }
  ]);
}

accounting/accounting.js

configureRouter(config, router) {
  config.map([
    { route: '', redirect: 'home' },
    { route: 'home',      name: 'accounting-home',              moduleId: 'accounting/accounting-home' },
    { route: 'accounts',  name: 'accounting-accounts',          moduleId: 'accounting/accounts/accounts' },
    { route: 'entries',   name: 'accounting-entries',           moduleId: 'accounting/entries/entries' }
  ]);
  this.router = router;
}

accounting/accounts/accounts.js

configureRouter(config, router) {
  config.map([
    { route: '',          redirect: 'list' },
    { route: 'list',      name: 'accounting-account-list',              moduleId: 'accounting/accounts/account-list' },
    { route: 'view/:id',  name: 'accounting-account-view',              moduleId: 'accounting/accounts/account-view' }
  ]);
  this.router = router;
}

I want to navigate to the third level, and it works to use normal link tags or type directly in the URL (e.g. <a href="#/accounting/accounts/view/2">) but none of the following approaches to named routes work:

  1. (from app.html, level 1 trying to access level 3) <a route-href="route: accounting-account-list">Account List</a>
  2. (from app.html, level 1 trying to access level 3) <a route-href="route: accounting-account-view; params.bind: {id: 2}">Account #2</a>
  3. (from accounting-home.js, level 2 trying to access level 3) this.router.navigateToRoute('accounting-account-view', {id: 2});
  4. (from accounting-home.js, level 2 trying to access level 3) this.router.navigateToRoute('accounting/accounting-accounts/accounting-account-view', {id: 2});

For #1-2, I'm getting console log errors like Error: A route with name 'accounting-account-list' could not be found. Check that name: 'accounting-account-list' was specified in the route's config. when the app loads, and the links are dead.

For #3-4, I'm getting console log errors like Uncaught Error: A route with name 'accounting-account-view' could not be found. I'm also getting lots of warnings like Warning: a promise was rejected with a non-error: [object Object] at _buildNavigationPlan in the console log when the page loads and every time I navigate.

What am I doing wrong? Do I need to do something special to access the routes of a different router level?

Side questions: Do I need to import {Router} from 'aurelia-router'; in every view-model, some, or none? Do I need to inject it?

Timofei answered 9/2, 2017 at 13:47 Comment(5)
I think you need to simplify your routing scheme! But seriously, I'll try to answer this later if nobody else has.Chekiang
Thanks! I'd appreciate the help!Timofei
@AshleyGrant, would you recommend I try to collapse into only two levels of routers?Timofei
If there are only those three routes in there, I'd be inclined to do that. It really depends on the full picture though. I haven't forgotten about this question. I've just been quite busy.Chekiang
Thanks. I'll start there. I am thinking that if I reduce to only two routers, I can probably use named routes within that router with no difficulties, but can't navigate to the parent router (or from the parent to the child) without some magic from you. ;-)Timofei
S
6

I encountered this same scenario about a week ago on a client project. Really, the only choice here is to create all of your routes upfront and then import them. You can mitigate this using switching roots via setRoot if you have separate sections (I'll explain further on).

My scenario is quite similar. I have a logged out view which has some routes; login, register, forgot-password and, page/:page_slug (this is for rendering static pages). And then I have a logged in dashboard view which has a tonne of routes.

Disclaimer: the following code examples have not been tested and should just be used as a guide. I also have used TypeScript instead of plain old Javascript as well. Converting the following to just Javascript (if needed) should be fairly straightforward. The following is just what I used, there might be better ways of doing this.

My logged in dashboard view has a lot of sections which need to be put into a tiered dropdown menu that resembles the following:

Users
    Add
    List
    Manage
Experiments
    Add
    List
    Manage
Activities
    Add
    List
    Manage
Ingredients
    Add
    List
    Manage
Account
    Profile
    Notifications
    Billing
    Settings

This is a menu shown on each page. The problem with child routers is the routes are not known ahead of time, they're dynamic (even though you define them). Only when you instantiate the view-model (ie. navigating to it) does Aurelia know about the routes.

The solution that I ended up choosing is not ideal, but it helps somewhat.

I broke out all of my non-auth/public facing routes into one file called: src/public-routes.ts

export default [
    {route: 'login', name: 'login', moduleId: './public/login'},
    {route: 'register', name: 'register', moduleId: './public/register'},
    {route: 'forgot-password', name: 'forgot', moduleId: './public/forgot-password'},
    {route: 'pages/:page_slug', name: 'page', moduleId: './public/page'},
];

Then I create a root file specifically for these public routes called src/public-app.ts with accompanying src/public-app.html (this contains the router view):

import PublicRoutes from './public-routes';

export class PublicApp {
    configureRouter(config: RouterConfiguration, router: Router) {
        config.map(PublicRoutes);
    }
}

Then I repeat the same for my private dashboard routes. Replacing instances of Public and public with Private and private respectively. A great tip for using the flat route approach is to not lump all of your routes into the one file if you have a lot of them.

What I ended up doing for my private routes was create a route file for each section. So my user routes have their own file user.routes.ts, my account routes have their own file account.routes.ts and so on. Then I import them into my main file (in this case private-routes.ts).

Then inside of my src/main.ts file I switch out the roots depending on whether or not the user is logged in:

export async function configure(aurelia: Aurelia) {
  aurelia.use
    .standardConfiguration()
    .feature('resources');

  let auth = aurelia.container.get(Auth);
  let root = auth.isLoggedIn ? './private-app' : './public-app';

  await aurelia.start();
  aurelia.setRoot(root);
}
Sexagenary answered 14/2, 2017 at 23:0 Comment(3)
Thanks Dwayne. So, you're suggesting this as an alternate approach to named routes across child routers... As best as you can tell this simply won't work in Aurelia since the routers don't know about each other?Timofei
Yep, this is an alternative approach to child routers entirely. You can name your routes still, there is just no way of being able to currently generate all of your routes upfront using child routers. They're good if you're happy to build your nav as you go like a breadcrumb menu, just not menus upfront like you're trying to do.Sexagenary
Coming in pretty late, but what are you thoughts on this approach?Septimal

© 2022 - 2024 — McMap. All rights reserved.