jwx / aurelia-vnext-history-browser-test-app

0 stars 1 forks source link

Routing with Aurelia #3

Open jwx opened 5 years ago

jwx commented 5 years ago

Routing

Navigation in Aurelia is handled by viewports and components. In short, you put one or more viewports in your application and then you specify their content using links or code. Whenever someone navigates to a new state of viewport components an entry will be added to the browser history, making it possible to use the browser's back, forward and refresh and get the right set of components. Let's take a look at an example.

Viewports

For the start view of our app, we're going to use two viewports:

<!-- app.html -->
<body>
  <nav>
    <!-- We'll get back to this -->
  </nav>

  <viewport name="lists" used-by="authors,books" default="authors"></viewport>
  <viewport name="content" default="about"></viewport>
</body>

The first viewport we'll give the name lists and specify that it's only used by the components/custom elements Authors and Books. We also set Authors as the default component, meaning that if nothing is specifically set for this viewport, it will display the Authors component. For the second viewport we won't specify anything except giving it the name content and setting the default to the About component. Convention will then allow all components not used by another viewport to use it. Note: While it's not necessary to explicitly name a viewport (it will then be named default by convention), it is recommended. Especially if you have more than one viewport in your application.

That's it for setting up the viewports. Now let's look at how to navigate and change the components in them.

Navigation

Navigation can be triggered in html through anchor tags or in code with the router's goto method. (There are more navigation methods available in the router, but we'll stick to goto for now.) A navigation segment in it's basic form consists of three parts: which component to show, in which viewport to show the component and what parameters to pass into the component, like this

"component@viewport=parameters"

or this

router.goto(Component, 'viewport', parameters);

But sometimes you don't want to pass parameters to the component, so that part is optional. And if the router can deduce where a component should go by itself, for example based on the viewport settings we did earlier, viewport can also be left out. So if you're using only one viewport, or have done a configuration similar to the one we did, you don't have to think about the viewport when navigating. Our nav part from above could then be

  <nav>
    <ul>
      <li><a href="about">About</a></li>
      <li><a href="authors+about">Authors</a></li>
      <li><a href="books+about">Books</a></li>
    </ul>
  </nav>

What's happening here then? The about is easy enough to understand, but what's the +about part after authors and books? Well, a navigation instruction can consists of several navigation segments, separated by +, meaning that we can change the components in more than one viewport in the same navigation instruction. The authors+about instruction is in its full form authors@lists+about@default, but since both authors and books are used by the lists viewport we don't have to specify viewport name for them. And since those two are the only components that can use the lists viewport, all other components, including about, has nowhere to go but the default viewport so we don't have to specify viewport for them either.

And that's it! We've now set up our main application navigation.

Parameters

Quite often you'll want to pass parameters when navigating to a component. In our example, the lists viewport might show the Books component which displays a list of books and if someone clicks on the title of a book, we want to display details about that book in our content viewport.

So we'll have the Books component fetch a list of books from somewhere and then display it with this

<ul>
  <li repeat.for="book of books"><a href="book=${book.id}">${book.title}</a></li>
</ul>

which could result in

<ul>
  <li><a href="book=1">The Colour of Magic</a></li>
  <li><a href="book=2">Jingo</a></li>
  <li><a href="book=3">Night Watch</a></li>
</ul>

The href attribute contains the navigation instruction and basically says "show the Book component where book is 1". Since the lists viewport is only available for the Authors and Books components, the Book component will be loaded into the only remaining viewport, content. When the router loads the Book component it will call a method called enter in it. This method will receive the parameter(s) specified in the href in a list as the first argument.

enter(parameters) {
  id = parameters.length ? parameters[0] : null;
  // Fetch book data for id ...
}

Named parameters

If we're not fond of unnamed parameters in a list, there's a simple way to get them named: add the name to the parameters in the href

<a href="book=id=1>The Colour of Magic"</a>

The first parameter to the enter method would now be an object:

enter(parameters) {
  id = parameters ? parameters.id : null;
  // Fetch book data for id ...
}

It's also possible to pass more than one parameter to the enter method.

<a href="book=id=1&language=sv>Magins färg"</a>
enter(parameters) {
  id = parameters ? parameters.id : null;
  language = parameters ? parameters.language : 'en';
  // Fetch book data for id and language...
}

Now, however, our hrefs are getting a bit verbose due to the names. Fortunately, there'a another way to specify the parameter names: add a static property called parameters to the component:

static parameters = ['id', 'language'];

enter(parameters) {
  id = parameters ? parameters.id : null;
  language = parameters ? parameters.language : 'en';
  // Fetch book data for id and language...
}

The href is now shortened to

<a href="book=1&sv>Magins färg"</a>

Most of the cases, though, you'll probably only pass one parameter, some kind of id, so that the href will read "show component where [component name] is [id value]".

That's it for navigating around and passing parameters between components!

Next we'll look into something that's a lot easier than it sounds: having components with their own viewports!

Vheissu commented 5 years ago

@jwx This is really nice. I like the simplicity of viewports for rendering components in this way. Easy to understand and just as powerful as the previous routing solution.

jwx commented 5 years ago

I should probably point out that the separators, @ and = (and the others that exist but aren't shown in these examples), isn't locked in. And that the plan is also to have them configurable so that developers can get a syntax they like.

bigopon commented 5 years ago

Probably the part that gives uneasy feeling about this is the mental model mapping of URL to viewport.

authors/about
# invoices/:id/items
invoices/1fdsf324-113n3-df314-1313123123/items

The first url can be explained into viewport quite comfortably. How should I think about the second one in this model? Apologize if explained elsewhere, I haven't followed closely.

jwx commented 5 years ago

It's good that you haven't followed closely since it results in the kind of questions I want. Your question would've been answered in the next step of the instruction, but I haven't written it yet. Briefly, how you should think is "the component InvoiceItems wants an id parameter", meaning

invoice-items=1fdsf324-113n3-df314-1313123123