Aurelia: [RFC] [Doc] Aurelia 2 routing compared to v1 (and other frameworks)

Created on 17 Aug 2019  路  13Comments  路  Source: aurelia/aurelia

馃挰 RFC

This document describes how the routing in Aurelia v2 differs from the routing in v1 (and other frameworks which uses a url-based routing). If you're new to client side routing, Routing with Aurelia might be a better starting point.

Two routers in one

In addition to having a new way of dealing with routing, the v2 router also supports the more traditional, url-based routing of v1. This document will however focus solely on the new component-based routing.

New mental model

The main difference between v1 and v2 routing is that in v1 you _compose a url string_ to tell the router, via an alias (a configured route), which components you want to load. In v2 you tell the router directly _which components to load_ and the router will take care of the rest, including creating the url string.

The url-based routing of v1 requires some kind of configuration to connect url strings to sets of components in viewports. The component-based routing of v2 doesn't require any "connect configuration" since we're working directly with the end result: which components to load.

View code example

v1

app.ts - configure the route/url string users to load Users component

public configureRouter(config: RouterConfiguration, router: Router) {
  config.map([
    { route: 'users', name: 'users', moduleId: './components/users', },
  ]);
}

app.html - link with route/url string above

<a href="#/users">Show users</a>
<router-view name="main"></router-view>

v2

app.ts - nothing is needed in app.ts

app.html - link directly with the name of the component to load

<a href="users">Show users</a>
<au-viewport name="main"></au-viewport>

How does the router know where the component is?


In Aurelia 2, all component dependencies must be registered either globally or on the component that has the dependency. This is a general principle and not just something for routing. The router uses the dependency registration to match component names to components. The actual location, the file, of the component is therefore never needed. You can read more about registering dependencies here.


Different names

While we've tried to avoid breaking changes, the differences in behavior have merited some name changes.

View list of commonly used names that have changed


In html

router-view  ->  au-viewport

In javascript/typescript

canActivate    ->  canEnter
activate       ->  enter
canDeactivate  ->  canLeave
deactivate     ->  leave
navigate       ->  goto
navigateTo     ->  goto

API Docs Needs more feedback RFC Router v1

Most helpful comment

Some live coded routing can be found here.

All 13 comments

href -> au-href

Might be confusing. It really hasn't changed. And route-href isn't au-href either. So maybe leave this part off for now.

@davismj You're right; removed it. Thanks!

While I really like the new v2 mental model for it's simplicity, We used the old configuration model for more than just pointing route X to component X, 3 things from the top of my head:

  • It's also the place where we would define stuff like authorization. Most of the time we would do something like this:
{
    route: ["task-update"],
    moduleId: "app/tasks/task-update",
    title: "Edit task",
    name: "task-update",
    settings: {
        roles: [Roles.Register, Roles.Planner, Roles.Administrator]
    },
},

With the new model, where would you put stuff like this? One solution would be to decorate your components with this security metadata, which seems fine. But you would still lose the flexibility of defining 2 routes pointing to the same component, where the authorization of route 1 differs from route 2 (for example, a create route and an update route, pointing to the same component)

  • You could tell the framework to execute a different activation strategy
{
    route: ["task-update"],
    moduleId: "app/tasks/task-update",
    title: "Edit task",
    name: "task-update",
    activationStrategy: activationStrategy.replace,
},

The same argument as above, you can also configure this on your component but you would be out of luck when you want to do one or the other depending on a specific route

  • It was easy to build a navbar from routeconfig, you could just iterate the config and build a navbar, throw in some custom properties to show/hide or build some hi毛rarchy. Will this be possible in this model? Perhaps by doing some AOT magic, building metadata about each route pointer in the app? Doesn't seem easy to pull off (to me :) )

I would like to repeat I really like the new model, but we need at least 2 of the things I mentioned on each project we do, so I'm interested in hearing your opinions on how you would tackle these scenario's in the new model.

The old model will also still be available, and to a certain extent the two will be usable in conjunction. This might be one of those cases where that would be the way to go.

The old model will also still be available, and to a certain extent the two will be usable in conjunction. This might be one of those cases where that would be the way to go.

I am fully aware of that, but my point implies we can never use the new model if stuff like this isn't possible, and I'm quite sure many others have the same requirements.

Also, I am looking into making the aurelia openid connect plugin compatible with V2, and right now I don't see a way to incorporate the new router into it. It would be a shame to require users to always use the configuration model if they want to use the OIDC plugin IMO

What I meant by

and to a certain extent the two will be usable in conjunction

Is that you could do stuff like this:

@route({ settings: { roles: [Roles.Register, Roles.Planner, Roles.Administrator] } })
export class TaskUpdate {

}

```ts
@route({ activationStrategy: activationStrategy.replace })
export class TaskUpdate {

}

To associate additional settings with a particular component via configuration, while relying on the new model to figure out the rest (path, name, etc) by convention.

The general idea behind the new model is that most configurable properties can be specified in the path / instruction without having to configure them. For some configurable properties this is either tricky to do, or for various reasons just not desirable (e.g. you might want to keep the url clean / looking more like traditional urls).

To address this point:

> But you would still lose the flexibility of defining 2 routes pointing to the same component, where the authorization of route 1 differs from route 2 (for example, a create route and an update route, pointing to the same component)

This could be addressed in several ways. The first one, being to define this on the component itself with two separate route configs:

```ts
@route({ path: ':id', settings: { roles: [Roles.Editor] } })
@route({ path: 'new', settings: { roles: [Roles.Anonymous] } })
export class EditThing {

}

This is an example of the old and new model working in conjunction. Only the path is specified along with custom settings to be able to differentiate the two, but this is on the component itself. There is still no configuration on the "parent" side.

That said, there could be configuration on the "parent" side regardless, even combined with some config on the component itself:

{
  path: ':id',
  settings: { roles: [Roles.Editor] },
  component: EditThing,
},
{
  path: 'new',
  settings: { roles: [Roles.Anonymous] }
  component: EditThing,
},

// ...

// Not differentiated by path, so applicable to all paths
@route({ activationStrategy: activationStrategy.replace })
export class EditThing {

}

@arnederuwe The new router comes with _routing hooks_. There'll be several of them, covering different parts of "centralized configuration". Currently, the api is

router.addHook(callbackMethod, options);

where callbackMethod is a method you provide with the logic you want for the specified hook. Parameters into and return value from the callback method varies depending on which hook type it is. The options in the options parameter also varies depending on hook type, but one is consistent: type, that specifies which hook type it is.

So, for your first two use cases I'd say the BeforeNavigation hook could be useful. For example

  this.router.addHook((instructions: NavigationInstruction[]) => 
  {
    for (const instruction of instructions) {
      if (!this.verifyAccess(instruction.componentName)) {
        return false; // Prevents the navigation, it's also possible to redirect
      }
    }
    return true; // Proceed
  }, 
  {
    type: HookTypes.BeforeNavigation, // Default, can be omitted in this case
    exclude: [ '', 'login' ], // Exclude public components from this hook. There's also an `include` option
  });

checkAccess(component) {
  const requirements = {
    'task-view': [Roles.View],
    'task-update': [Roles.Register, Roles.Planner, Roles.Administrator],
  }[component];
  for (const requirement of requirements) {
    if (user.roles.some(role => role === requirement)) {
      return true;
    }
  }
  return false;
}

Okay, pretty ugly code, but hopefully it illustrates the possibilites regarding centralized configuration.

As for the navigation, any list structure will suffice to iterate through.

<nav>
  <ul>
    <li repeat.for="link of [{ link: 'tasks', title: 'Tasks' }, { link: 'closed-tasks', title: 'Closed tasks' }]">
      <a goto="${link.link}">${link.title}</a>
    </li>
  </ul>
</nav>

There's also an au-nav custom element that lets you do this in code

<au-nav name="tasks-menu"></au-nav>
  router.setNav('tasks-menu', [{ component: 'tasks', title: 'Tasks' }, { component: 'closed-tasks', title: 'Closed tasks' }]);

The au-nav also supports child menues (children) and styling options. You might want to keep full control for complex menues and use cases, but for basic use cases it's a way to quickly and easily get a navbar (or tab list!) going.

Bottom line is that the goal is that if you're able to configure it centrally in v1, you should be able to do it with the new routing approach as well (where it makes sense).

Thank you @fkleuver and @jwx !
You provided me with everything I need in order to start putting something together, it seems I was missing some critical information about the new model, all cleared up now :)

To be clear, the APIs I mentioned don't exist yet. That's what I'll be working on towards the end of this month and beginning of January.

As mentioned on Discord, That's fine :)
Give me a ping when this is ready to testdrive

Im also trying to put some routing example together, but it seems none of the is already available? Or am I looking in the wromg place?

I've never liked how popular routers required me to manually specify url strings in config files and then remember and refer to them in HTML. Those urls had to be maintained manually and generally had no support for intellisense or strong typing for correctness in IDEs.

I think the new approach is a good step forward (or a step towards what non-web development had in early 2000's :smiley: )

Some live coded routing can be found here.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bigopon picture bigopon  路  7Comments

CuddleBunny picture CuddleBunny  路  10Comments

EisenbergEffect picture EisenbergEffect  路  10Comments

HamedFathi picture HamedFathi  路  9Comments

HamedFathi picture HamedFathi  路  13Comments