Aurelia with an ASP.NET Core API: Modeling and Displaying Data in Aurelia

This is the third entry in a series using Aurelia and ASP.NET Core together. Each post builds on the previous and all the code is available on Github.

Part 1 – Add Aurelia to an ASP.NET Core Project
Part 2 – Aurelia with an ASP.NET Core API
Part 3 – Aurelia with an ASP.NET Core API: Modeling and Displaying Data in Aurelia (this post)
Github repo with the code for all of the parts with a release to go with each post

Starting point

Starting with the code from last weeks post we have a single solution with two projects. The Contacts project contains a basic razor UI for CRUD operations related to contact management as well as an API to provide that contact data to other applications.

The Aurelia project is a MVC application with Aurelia. At the moment the MVC and Aurelia applications don’t interact. In its current state the Aurelia application will connect to the Contacts API, download a list of contacts, and display the names of the contacts.

The goal

This post will cover taking the data from the Contacts API and mapping it to a JavaScript model class. Next the existing display of contacts will be removed and replaced with a contact list component.

Create a model

Create a contacts folder inside the src folder of the Aurelia project. Next add a contact.js file. This will be the model of a contact in the system. At the moment it only contains a constructor and a getAddress function. getAddress is just a demonstration of the model providing some functionality and not just being a data container.

export class Contact {
    constructor(data) {
        Object.assign(this, data);
    }

    getAddress() {
        return `${this.address} ${this.city}, ${this.state} ${this.postalCode}`;
    }
}

The Contact class ends up with all the properties of the data that was past to the constructor. In this case is all the properties from the Contact class in the Contact project. Coming from a mostly C# background the dynamic nature of JavaScript takes a little bit of getting used too.

 File naming an view/view model location

I hit a problem with how my files were named and Aurelia’s view/view model location strategy. I haven’t found a list of the conventions, but here is what I found playing around based on a view model named ContactList.

Filename Element Located
ContactList contact-list No
Contact-List contact-list No
Contact-List Contact-List No
contactlist contactlist No
contact-list contact-list Yes

Had the class name been Contactlist then the contactlist in the list above would have worked. For more information on how view are located check out this section of the Aurelia documentation.

Renaming for consistency

Based on research into why a view was not being located I am doing a bit of reorganizing in the project. All the contact related files are moving to a new contacts folder and the contactService.js is being renamed to contact-service.js. This is following the idea of organizing code by feature instead of type of file.

Update the Contact Service to use the new Contact class

In the contacts folder open the contact-service.js file. Next add an import for the Contact model class.

import { Contact } from './contact';

Next to the GetAll function add a line to convert the data to a Contact.

GetAll() {
   return this.http.fetch('')
        .then(response => response.json())
        .then(contacts => Array.from(contacts, c => new Contact(c)))
        .catch(error => console.log(error));
}

Here is the complete contact-service.js file.

import { HttpClient } from 'aurelia-fetch-client';
import { Contact } from './contact';

export class ContactService {
    static inject() { return [HttpClient] };

    constructor(http) {
        this.http = http;

        this.http.configure(config => {
            config
                .useStandardConfiguration()
                .withBaseUrl('https://localhost:13322/api/contactsApi/');
        });
    }

    GetAll() {
       return this.http.fetch('')
            .then(response => response.json())
            .then(contacts => Array.from(contacts, c => new Contact(c)))
            .catch(error => console.log(error));
    }
}

To verify the returned results are actually using the Contact model class change the call in App.js to use the getAddress function instead of just printing the contact names.

constructor(contactService) {
    this.message = 'Hello World!';
    contactService.GetAll()
        .then(result => {
            console.log(result);
            this.message = `Contact Results: 
                            ${result.map((contact) => contact.getAddress())}`;
        });
}

Run the application and it will print customer addresses. Note that the URL for the Aurelia application is http://localhost:37472/index.html.

Adding a contact list

Add two new files to the contacts folder for contact-list.html and contact-list.js which will result in the following structure.

contactrenames

The view model

contact-list.js is the view model for the contact list and will handle calling the ContactService to get a list of contacts to display. The contact service needs to be imported and injected via the constructor. Additionally the constructor is setting up an array that will be used to store the contacts after they are retrieved.

The call to the contact service is handled in the created function which is automatically called as part of Aurelia’s component lifecycle. For more information on the component lifecycle see the official documentation here. The following is the full definition of the ContactList view model class.

import { ContactService } from './contact-service';

export class ContactList {
    static inject() { return [ContactService] };

    constructor(contactService) {
        this.contactService = contactService;
        this.contacts = [];
    }

    created() {
        this.contactService.GetAll()
            .then(contacts => this.contacts = contacts);
    }
}

The view

contact-list.html is the view that Aurelia will map and use with contact-list.js. As before this view is going to be very basic to keep the noise down. The view is a template with an unordered list of contact names and their addresses.

<template>
    <ul>
        <li repeat.for="contact of contacts">
            <h4>${contact.name}</h4>
            <p>${contact.getAddress()}</p>
        </li>
    </ul>
</template>

The repeat.for tells Aurelia to output a list item for each contact found in the contacts property of the view model. ${contact.name} is a one way binding to the name property of the current contact. Also notice ${contact.getAddress()} which is one way binding the result of a function from the Contact model class.

Displaying a component

Now the component needs to be displayed. For simplicity the contact list will be shown directly from the main application view (app.html). The sample from last week will need to be cleared out before adding the contact list view. In the end the view should contain the following.

<template>
    <require from="./contacts/contact-list"></require>
    <h1>App</h1>
    <contact-list></contact-list>
</template>

require from is importing the contact list and then the contact-list tag determines where the contact list will show. Aurelia makes all components available in this manner. They just needs to be required in to be used as a tag.

Finally make sure to clear out app.js if using the sample from last week as retrieving contact list data has been moved to the contact list view model.

export class App {
}

When the application needs it the App class is where the application level router would go.

Wrap up

Aurelia is always a pleasant surprise after being away from it for awhile. After getting project setup and conventions down it is always pleasant to use. The documentation is very good for the most part. As you can tell from this post I had some trouble with conventions which is something I wish was covered better in the docs, and if it is and I just missed it please leave a comment.

The code associated with this post can be found here.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.