Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using named routes with Aurelia child routers

Tags:

aurelia

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?

like image 606
LStarky Avatar asked Feb 09 '17 13:02

LStarky


1 Answers

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);
}
like image 94
Dwayne Charrington Avatar answered Jan 02 '23 18:01

Dwayne Charrington