Expanding on this post where a placeholder was added for contact creation the placeholder will be replaced with an actual UI. As part of the contact creation process, Aurelia’s fetch client will be used to make a post request to the ASP.NET API. The code at the starting point can be found here. If using the sample code keep in mind all the changes in this post takes place in the Aurelia project.
Contact service changes to allow post
In this project a service is used to keep all the Http bits isolated from the rest of the application. In the contactService.ts file found in the ClientApp/app/components/contacts/ directory a couple of changes need to be made. First the fetch import needs to expose json in addition to HttpClient.
import { HttpClient, json } from 'aurelia-fetch-client';
Then a save function is added that makes a post request to the ASP.NET API and return a new contact based on the response from the post request. The contact in the post response will contain the ID assigned by the API.
save(contact: Contact): Promise<Contact> { return this.http.fetch('', { method: 'post', body: json(contact) }) .then(response => response.json()) .then(contact => new Contact(contact)) .catch(error => console.log(error)); }
Notice the usage of json to serialize the contact being create to JSON before sending to the server. Also, note that just logging an error to the console isn’t a best practice and should be handling in a different way in a production application.
Contact detail view model
The view model that backs the contact detail view needed a function to allow saving of a contact. The following code uses the contact service to save a contact and then replace its local contact with the new one returned from the API. Finally, the class level variable indicating if the view model is in create or detail mode is set to true which triggers the UI to change out of create mode.
save() { this.contactService.save(this.contact) .then(contact => this.contact = contact) .then(() => this.hasContactId = true); }
You will see in the sample code that a second function was added for reset which is a quick way to reset the create UI.
reset() { this.contact = new Contact(); }
Contact model
The constructor of the contact class changed to make the data parameter optional to allow for the creation of an empty contact.
constructor(data?) { if (data == null) return; Object.assign(this, data); }
Contact detail view
The view under when the most changes and the following is the entirety of the UI file which can be found in the contactDetail.html file. The code will be followed up with call outs for the important bits.
<template> <h1>Contact Details</h1> <hr /> <div if.bind="hasContactId"> <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> <div if.bind="!hasContactId"> <div> <form role="form" class="form-horizontal"> <div class="form-group"> <label class="col-sm-2 control-label">Name</label> <div class="col-sm-10"> <input type="text" placeholder="name" class="form-control" value.bind="contact.name" /> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">Address</label> <div class="col-sm-10"> <input type="text" placeholder="address" class="form-control" value.bind="contact.address" /> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">City</label> <div class="col-sm-10"> <input type="text" placeholder="city" class="form-control" value.bind="contact.city" /> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">State</label> <div class="col-sm-10"> <input type="text" placeholder="state" class="form-control" value.bind="contact.state" /> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">Zip</label> <div class="col-sm-10"> <input type="text" placeholder="zip" class="form-control" value.bind="contact.postalCode" /> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">Phone</label> <div class="col-sm-10"> <input type="text" placeholder="phone" class="form-control" value.bind="contact.phone" /> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">Email</label> <div class="col-sm-10"> <input type="email" placeholder="email" class="form-control" value.bind="contact.email" /> </div> </div> </form> </div> <div class="text-center"> <button class="btn btn-success btn-lg" click.delegate="save()">Save</button> <button class="btn btn-danger btn-lg" click.delegate="reset()">Reset</button> </div> </div> <div> <a route-href="route: contactlist">Back to List</a> </div> <hr /> </template>
All control of content rendering has been changed to use hasContactId.
Default view: <div if.bind="hasContactId"> Create view: <div if.bind="!hasContactId">
For the creation UI, the data is bound using Aurelia’s value converters for more detail see the docs. The value converter is the value.bind bit.
<input type="text" placeholder="name" class="form-control" value.bind="contact.name" />
The last thing to point out is the click delegates that are used to call the associate save and rest functions with the Save and Reset buttons are clicked.
<button class="btn btn-success btn-lg" click.delegate="save()">Save</button> <button class="btn btn-danger btn-lg" click.delegate="reset()">Reset</button>
Wrapping up
The application now has the ability to add contact instead of only viewing existing contact which brings it close to a more realistic application. The code in its finished state can be found here.
The plan is to continue iterating on this application and moving the Aurelia and Angular 2 projects in parallel. I hope this is useful and if you have any specific features you would like to see implemented leave a comment.