Refactor

Refactor Round 2

In last week’s post accessing a web API was refactored to a central service in an Aurelia application instead of being spread across the application. In this post we will push the refactor even further. The service will change from a pass through to the backing web API to be the data provider for the Aurelia application.

The first set of changes is to the service, which may be a bad name with the changes that are being made. GetAll will now keep a copy of the contact retrieved for the web API.

Before:
GetAll() {
    return this.http.fetch('contacts')
        .then(response => response.json())
        .catch(error => console.log(error));
}
After:
GetAll() {
    return this.http.fetch('contacts')
        .then(response => response.json())
        .then(contacts => this.contacts = contacts)
        .catch(error => console.log(error));
}

As an example of the implications of this change we will look at the Get function which require a contact ID. Instead of hitting the web API to pull the details of contact the information is pull from the service’s local copy that was cached in the GetAll function above.

Before:
Get(id) {
    return this.http.fetch(`contacts/${id}`)
        .then(response => response.json())
        .catch(error => console.log(error));
}
After:
Get(id) {
    return new Promise((resolve) => {
        resolve(this.contacts.filter(contact => contact.Id == id)[0])
    });
}

With the above changes the view model for the contact list contains very little code. With the binding of the contact list now bound to the service changes to contacts are automatically reflected in the contact list. This removed the need for the event aggregator. The full code for the list view model follows.

import {inject} from 'aurelia-framework';
import {ContactService} from 'Aurelia/contactService';

@inject(ContactService)
export class List {

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

All the view models had similar reductions in the amount of code required. There are many more options on the best way to factor this code. A next step could be to keep the web API access separate from the a new business class used to manage and cache the contact data once it has been retrieved.

Refactor Round 2 Read More »

Refactor of ASP.NET 5 Web API Access from Aurelia

This post is going to cover refactoring of client side code based in Aurelia to centralize access to an ASP.NET 5 web API. This will remove the need for the fetch and configuration APIs in most classes which will be replaced by access via a contact service. This is of course based on the same contacts application I have been using in most of my previous posts. I am going to limit the examples to what is need to display a contact list, but the same concepts would apply to all aspects of contact interaction.

To begin I created a new ContactService.js file which will be used to handle all interactions with the ASP.NET 5 web API.

import {inject} from 'aurelia-framework';
import {HttpClient, json} from 'aurelia-fetch-client';
import 'fetch';
import {EventAggregator} from 'aurelia-event-aggregator';
import {Configure} from 'aurelia-configuration';
import {ContactUpdatedMessage} from 'Aurelia/ContactUpdatedMessage';

@inject(HttpClient, EventAggregator, Configure)
export class ContactService {

    constructor(http, eventAggregator, configAurelia) {
        http.configure(config => {
            config
                .useStandardConfiguration()
                .withBaseUrl(configAurelia.get('api.baseUrl'));
        });

        this.http = http;
        this.eventAggregator = eventAggregator;
    }

    GetAll() {
        return new Promise((resolve) => {
            resolve(this.http.fetch('contacts')
                .then(response => response.json(),
                    err => console.log(err)));
        });
    }
}

The above class utilizes the configuration for the API base URL as explained in this post. The event aggregator is also included and would be used to notify of changes on a contacts. The event aggregator is not actually used in the shown code but example usage can be found here.

Note that the GetAll function returns a promise which is built off of the promise that the http fetch returns. Promises help with async operations are needed and are what enabled the then() function that makes reacting when an async operation is completed very simple. They are not the subject of this post but I wanted to make special note of them because they are new in the latest version of JavaScript.

Next are the changes to the list class which is the view model for the contact list. All the http related imports get removed and an import for the contactService is added. For classes don’t need notification of contact updates the EventAggregator and ContactUpdatedMessage could be removed as well.

Before:
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import {EventAggregator} from 'aurelia-event-aggregator';
import 'fetch';
import {ContactUpdatedMessage} from 'Aurelia/ContactUpdatedMessage';
import {Configure} from 'aurelia-configuration';
After:
import {inject} from 'aurelia-framework';
import {EventAggregator} from 'aurelia-event-aggregator';
import {ContactUpdatedMessage} from 'Aurelia/ContactUpdatedMessage';
import {ContactService} from 'Aurelia/contactService';

In the inject decorator HttpClient and Configure are replaced with ContactService.

Before:
@inject(HttpClient, EventAggregator, Configure)
After:
@inject(EventAggregator, ContactService)

Next the constructor changed to remove http related items and to save a reference to the ContactService.

Before:
constructor(http, eventAggregator, configAurelia){
    http.configure(config => {
        config
          .useStandardConfiguration()
          .withBaseUrl(configAurelia.get('api.baseUrl'));
    });

    this.http = http;
    eventAggregator.subscribe(ContactUpdatedMessage, message => {
        let updatedContact = 
	     this.contacts.filter(contact => contact.Id == message.contact.Id)[0];
        Object.assign(updatedContact, message.contact);
    });
}
After:
constructor(eventAggregator, contactService){
    eventAggregator.subscribe(ContactUpdatedMessage, message => {
        let updatedContact = 
             this.contacts.filter(contact => contact.Id == message.contact.Id)[0];
        Object.assign(updatedContact, message.contact);
    });
    this.contactService = contactService;
}

Finally the direct call to the web API is replaced with a call to the ContactService.

Before:
created(){
    return this.http.fetch('contacts')
      .then(response => response.json())
      .then(contacts => this.contacts = contacts);
}
After:
created(){
    return this.contactService.GetAll()
      .then(contacts => this.contacts = contacts);
}

Following the above example, the remaining locations where the web API is accessed directly can be replaced with the ContactService.

Update

Something hit me when reading this post today. Aurelia’s fetch client returns a promise so there is no need to create a new promise in the ContactService. Here is the GetAll function from above updated to not declare a new promise.

Before:
GetAll() {
    return new Promise((resolve) => {
        resolve(this.http.fetch('contacts')
            .then(response => response.json(),
                err => console.log(err)));
    });
}
After:
GetAll() {
    return this.http.fetch('contacts')
        .then(response => response.json())
        .catch(error => console.log(error));
}

There maybe case where creating is a new promise is the correct thing to do, but in this case it was unnecessary.

Refactor of ASP.NET 5 Web API Access from Aurelia Read More »