Aurelia – Router links, click delegate, routing parameters
This post is going to cover multiple of topics that I hit while creating a contact detail page to go along with the contact list that is part of my ASP.NET Basics repo. The code before any changes can be found here. If you are following along with the sample application keep in mind all the changes in this post take place in the Aurelia project.
Creating a detail view and view model
Create contactDetail.html file inside of ClientApp\app\components\contacts. This is the view that will be used to display all the details of a specific contact as well as a link back to the contact list. The following image shows the folder structure with the view and view model already added.
View
The following is the full contents of the view. This will be followed up few a calls out of Aurelia specific things going on.
<template> <h1>Contact Details</h1> <hr /> <div if.bind="contact"> <dl class="dl-horizontal"> <dt>ID</dt> <dd>${contact.id}</dd> <dt>Name</dt> <dd>${contact.name}</dd> <dt>Address</dt> <dd>${contact.getAddress()}</dd> <dt>Phone</dt> <dd>${contact.phone}</dd> <dt>Email</dt> <dd>${contact.email}</dd> </dl> </div> <a route-href="route: contactlist">Back to List</a> <hr /> </template>
if.bind will keep the contact details out of the DOM if the condition fails. In this case, if the contact is null.
${value} is one of Aurelia’s bind syntaxes. For more details check out their docs.
<a route-href=”route: contactlist”> is using Aurelia’s router to generate a link back to the contact list component.
View model
Next, create a contactDetail.ts file inside of ClientApp\app\components\contacts which is the view model that goes with the view created above.
The view model gets an instance of the ContactService injected which is used to pull a contact’s detail information during the activate lifecycle hook. Notice the first parameter of the function (parms) which is where the route parameters are passed in.
import { inject } from 'aurelia-framework'; import { Contact } from './contact'; import { ContactService } from './contactService'; @inject(ContactService) export class ContactDetail { contact: Contact; constructor(private contactService: ContactService) { } activate(parms, routeConfig) { return this.contactService.getById(parms.id) .then(contact => this.contact = contact); } }
Adding get by ID to the Contact Service
The existing ContactService doesn’t provide a function to get a contact by a specific ID so one needs to be added.
The following calls the API in the Contacts project and uses the result to create an instance of a Contact as a promise which is returned to the caller.
getById(id: string): Promise<Contact> { return this.http.fetch(id) .then(response => response.json()) .then(contact => new Contact(contact)) .catch(error => console.log(error)); }
Add a route with a parameter
Next, the router needs to be made aware of the new contact details. Open app.ts inside of the ClientApp/app/components/app folder. This file contains all the routes for the application. The bit we are interested in is the config.map which is an array of routes the application handles. Contact details is a new route which means adding a new object to the config.map array.
{ route: 'contact-detail/:id', name: 'contactdetail', moduleId: '../contacts/contactDetail', nav: false, title: 'Contact Detail' }
The route is the pattern used to match URLs. In addition, parameters can be used in the form of :parameterName. The above route will handle requests for http://baseurl/contact-detail/{id} where {id} is an ID of a contact. If a route has a parameter it will be made available to the activate function via the parms parameter in the view model.
moduleId is used to locate the view/view model that goes with the route.
nav controls if the route will be included in the routers navigation model. This is used to build the menu in this application. Contact details shouldn’t show in the menu which is why nav is set to false. For more details on Aurelia’s router check out the docs.
Integrating the detail view with the contact list
The contact list view and view model needed changes to support the contact detail page.
View model
The change to the view model was the simplest. A variable for the ID of the select contact was added as well as a function that gets called when a contact is selected. The following is the fully contact list view model.
import { inject } from 'aurelia-framework'; import { Contact } from './contact'; import { ContactService } from './contactService'; @inject(ContactService) export class ContactList { contacts: Contact[]; selectedContactId: number = null; constructor(private contactService: ContactService) {} created() { this.contactService.getAll() .then(contacts => this.contacts = contacts); } select(contact) { this.selectedContactId = contact.id; } }
The changes to the view model were not really required to add the contact detail page, but they are there to show how to setup a click delegate on the view side. In the future, the selected contact could come in handy if the list and details were shown at the same time.
View
On the view, the amount of data being displayed was reduced to show just contact ID and name. A column was added with a link to the details page that is now used to show the rest of information about a contact. The following is the full view.
<template> <h1>Contact List</h1> <p if.bind="!contacts"><em>Loading...</em></p> <table class="table" if.bind="contacts"> <thead> <tr> <th>IDs</th> <th>Name</th> <th></th> </tr> </thead> <tbody> <tr repeat.for="contact of contacts" class="${contact.id === selectedContactId ? 'active' : ''" }> <td>${contact.id}</td> <td>${contact.name}</td> <td><a route-href="route: contactdetail; params.bind: {id:contact.id}" click.delegate="select($contact)"> Details </a> </td> </tr> </tbody> </table> <hr /> </template>
click.delegate=”select($contact)” will cause the select function of the view model to be called when the associated element clicked and passes the relevant contact object. The docs go into more depth on when to use delegates vs triggers.
The details link contains a lot of concepts. route-href is binding the anchor to Aurelia’s router.
route: contactdetail is telling the router which route to load.
params.bind: {id:contact.id} is telling the link to pass the contact’s ID to through the router to the view model that is being loaded. The following is the activate function of the contact detail view model as a reminder of the parameter’s usage.
activate(parms, routeConfig) { return this.contactService.getById(parms.id) .then(contact => this.contact = contact); }
Wrapping up
This post cover a lot of topics, but they were all things I had to review in the course of adding contact details. My hope is this post will shortcut your own research and get you back to your task at hand. The finished code can be found here.
Leave a comment with any thoughts and/or questions.
Aurelia – Router links, click delegate, routing parameters Read More »