Changing the router title

Hi.
I’ve got a very simple nav-bar, i.e.

<template>
  <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
    <div class="navbar-header">
      <a class="navbar-brand" href="#">
        <i class="fa fa-home"></i>
        <span>${router.title}</span>
      </a>
    </div>
  </nav>  
</template>

What I would like is for the router’s title to change depending on the current view. I.e. the “home” menu text changes depending on the view. The application has three views, one “overview” view (route [’’, ‘home’]) and one “details” view and one graph view. For the two latter views I would like the “home” menu text to be e.g. something like 'To the overview". Is this possible? If so, how could it be implemented?

TIA

norgie

For these kinds of questions, it might be useful to specify which version of Aurelia you are currently using (either v1 or v2-alpha).

I have no relevant experience with v2 yet, but I do know that its routing mechanisms have been revamped considerably. I will try to make some suggestions based on Aurelia v1 here, but perhaps they will also (partly) apply to v2.

My first thought would be to make use of fitting Router Events in your nav-bar component. But perhaps that scatters your logic too much. You probably don’t want any view-related logic inside your nav-bar.

So instead you might consider to create a custom event that will be dispatched from your view(s) and will be handled by the nav-bar. In the event object, you could pass the text value to display in your nav-bar header. That way the view stays in control of what should be shown in the nav-bar header.

To overcome the issue that you would need to dispatch the custom event in every view you use, you might actually combine the two above strategies:

  1. Let the nav-bar handle a Router Event (probably the router:navigation:processing event) to clear its header text or set it to a default value.
  2. Let the nav-bar handle a custom event to update its header text based on a string value that is supplied by a view that dispatched the new custom event (probably in its activate lifecycle callback).

I am not too familiar with the actual rendering process’ details, so perhaps this implementation could cause “flickering” of the nav-bar’s header text. If that is the case, you could consider to introduce a separate variable in your view-model to store the new header text from steps 1 and 2, and let the nav-bar handle an additional Router Event (probably the router:navigation:complete event) to update the nav-bar’s header text from that variable. Doing so, the nav-bar’s header text will certainly be updated only once when a new routed view is rendered.

For dispatching events in Aurelia, you might want to use the Event Aggregator. I guess the Event Aggregator should be used anyway in case you want your component(s) to respond to Router Events.

Hope this helps…

Edit:

Ah yes. If event handling logic would become too complicated, you could take a look at the Aurelia Store Plugin, which allows you to manage and track your application’s runtime state in a more advanced way.

Apologies for the missing version information but it just slipped my mind. However, your assumption about it being Aurelia v.1 was correct. Thank you for taking time to outline a few alternatives. I’ll explore them in more detail when I get the time to do so. Btw. the app. isn’t very advanced and I’m not too worried about scattered logic in this case.

Hi @norgie ,

I have been playing around a little with this issue.

First, I created an empty Aurelia project skeleton (using TypeScript) and added the following three placeholder views:

home.html:

<template>
  This is the Home view.
</template>

details.html:

<template>
  This is the Details view.
</template>

graph.html:

<template>
  This is the Graph view.
</template>

Then I created a new nav-bar component:

nav-bar.html:

<template>
  <ul>
    <li repeat.for="nav of router.navigation">
      <a href.bind="nav.href">${nav.title}</a>
    </li>
  </ul>
</template>

nav-bar.ts:

import { autoinject } from 'aurelia-framework';
import { Router } from 'aurelia-router';

@autoinject
export class NavBar {
  router: Router;

  constructor(router: Router) {
    this.router = router;
  }
}

and changed the app component’s code to this:

app.html:

<template>
  <require from="./nav-bar"></require>
  <nav-bar></nav-bar>
  <router-view></router-view>
</template>

app.ts:

import { PLATFORM } from 'aurelia-pal';
import { RouterConfiguration } from 'aurelia-router';

export class App {
  configureRouter(config: RouterConfiguration) {
    config.title = 'Aurelia';
    config.options.pushState = true;
    config.options.root = '/';
 
    config.map([
      {
        route: '', redirect: 'home'
      },
      {
        route: 'home', name: 'home', moduleId: PLATFORM.moduleName('home.html'),
        title: 'Home', nav: 1
      },
      {
        route: 'details', name: 'details', moduleId: PLATFORM.moduleName('details.html'),
        title: 'Details', nav: 2
      },
      {
        route: 'graph', name: 'graph', moduleId: PLATFORM.moduleName('graph.html'),
        title: 'Graph', nav: 3
      }
    ]);
  }
}

With this code in place, I was able to switch between the three views using the navigation component. So far so good.

Next, I started working on making the navigation’s home view’s title change to ‘To the homepage’ instead of just ‘Home’ when the details view or the graph view is rendered.

Turned out that I didn’t have to do anything with event handling at all! :sunglasses:

First, I configured the details and graph routes to include a custom setting called overriddenHomeTitle, which I set to 'To the homepage':

{
  route: 'details', name: 'details', moduleId: PLATFORM.moduleName('details.html'),
  title: 'Details', nav: 2,
  settings: {
    overriddenHomeTitle: 'To the homepage'
  }
},
{
  route: 'graph', name: 'graph', moduleId: PLATFORM.moduleName('graph.html'),
  title: 'Graph', nav: 3,
  settings: {
    overriddenHomeTitle: 'To the homepage'
  }
}

Next, I edited the nav-bar's HTML view to render these custom settings when appropriate.

The Router object has a currentInstruction property, which in turn contains a config property that represents the RouteConfig object specified in the router configuration in the app component. So when you have a Router object called router, you can get the overriddenHomeTitle custom setting of the current route with router.currentInstruction.config.settings.overriddenHomeTitle.

Of course, this value should only be overridden for the home entry in the navigation bar. It could be done using a ternary operator that checks if nav.config.name == 'home', but I chose to use some nasty && and || operator logic, which is a little more compact. :wink:

This is my resulting nav-bar component’s view:

nav-bar.html:

<template>
  <ul>
    <li repeat.for="nav of router.navigation">
      <a href.bind="nav.href">${(nav.config.name == 'home' && router.currentInstruction.config.settings.overriddenHomeTitle) || nav.title}</a>
    </li>
  </ul>
</template>

I made a Dumber Gist so that you can check it out.

Is this what you are looking for?

1 Like

Hi @norgie ,

Reflecting on my last answer, this is probably not what you want.

Looking at your original navbar’s HTML markup, it seems that you set its header text to the router’s title. Typically, the router’s title is something like your application’s short name.

Each route can have its own title too. When a route has a title as well, the browser’s title bar will display both the router title and the route’s title, by default separated with a pipe.

So if your application is called “MyApp” and you have your three views, I guess that your navbar will eventually show a header with the text “MyApp” (not sure if it should link anywhere, but if it should, it should probably link to your app’s root) and three smaller items called “Home”, “Details” and “Graph”, which link to the respective views.

It can be done to update/modify your navbar’s header text/title, but… do you really need to?

nav-bar.html:

<template>
  <!--header:-->
  <a route-href="route: home">${router.currentInstruction.config.settings.overriddenHomeTitle || getHomeRouteTitle()}</a>
  <!--items:-->
  <ul>
    <li repeat.for="nav of router.navigation">
      <a href.bind="nav.href">${nav.title}</a>
    </li>
  </ul>
</template>

nav-bar.ts:

import { autoinject } from 'aurelia-framework';
import { Router } from 'aurelia-router';

@autoinject
export class NavBar {
  router: Router;

  constructor(router: Router) {
    this.router = router;
  }

  getHomeRouteTitle() {
    return this.router.routes.find(r => r.name === 'home').title;
  }
}

Dumber Gist

3 Likes

Nice. Thank you. Highly appreciated.
:slight_smile:

Hi @Bart
Just got around to implement you solution and it worked like a charm. :slight_smile:
My menu has just got one item, so this is what it looks like now:

<template>
  <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
  	<div class="navbar-header">
		  <a class="navbar-brand" href="/">
        <i class="fa fa-home"></i>
        <span>${router.currentInstruction.config.settings.overriddenHomeTitle || getHomeRouteTitle()}</span>
      </a>
    </div>
  </nav>	
</template>
1 Like