Expanding on this post which created a placeholder for creating new contacts this post will create an actual UI and post the newly created contact to an ASP.NET Core API. The code before any changes can be pulled using this release tag. Keep in mind all changes in this post take place in the Angular project if you are using the associated sample.
Contact service changes to all post
The contract service found in the contact.service.ts file of the ClientApp/app/components/contacts/ directory is used to encapsulate interaction with the associated ASP.NET Core API and prevent access to Angular’s Http library from being spread across the application. The first change is to expand the existing import of Angular’s Http library to include Headers and RequestOptions.
import { Http, Headers, RequestOptions } from '@angular/http';
Next, a save is added that makes a post request to the ASP.NET Core API with the body set to a JSON version of the contact to be added.
save(contact: Contact): Promise<Contact> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); return this.http.post(this.baseUrl, JSON.stringify(contact), options) .toPromise() .then(response => response.json()) .then(contact => new Contact(contact)) .catch(error => console.log(error)); }
The API will return the created contact with the ID now filled in which will be returned to the caller. As I have said before catching the error and just writing it to the console isn’t the proper way to handle errors and this should be done in a different manner for 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); }
Include Angular Forms
For the two-way binding needed on the contact detail page Angular forms will be used. To include them in the project open the app.module.ts file in the ClientApp/app/ directory. Add the following import.
import { FormsModule } from '@angular/forms';
Then add FormsModule to the imports array.
imports: [ UniversalModule, FormsModule, RouterModule.forRoot([
Contact detail view
The following is the full contact view as it stands with all of the needed changes made. This will be followed by call outs of some of the important items.
<h1>Contact Details</h1> <hr /> <div *ngIf="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 *ngIf="!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" [(ngModel)]="contact.name" name="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" [(ngModel)]="contact.address" name="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" [(ngModel)]="contact.city" name="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" [(ngModel)]="contact.state" name="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" [(ngModel)]="contact.postalCode" name="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" [(ngModel)]="contact.phone" name="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" [(ngModel)]="contact.email" name="email" /> </div> </div> </form> </div> <div class="text-center"> <button class="btn btn-success btn-lg" (click)="save()">Save</button> <button class="btn btn-danger btn-lg" (click)="reset()">Reset</button> </div> </div> <a routerLink="/contact-list">Back to List</a> <hr />
All control of content rendering has been changed to use hasContactId.
Default view: <div *ngIf="hasContactId"> Create view: <div *ngIf="!hasContactId">
For the creation UI, the data is bound using Angular’s ngModel binding.
<input type="text" placeholder="address" class="form-control" [(ngModel)]="contact.address" name="address" />
If you have any issues make sure and check that you have the name attribute set to the property you are wanting to bind to.
The last thing to point out is the click handlers 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)="save()">Save</button> <button class="btn btn-danger btn-lg" (click)="reset()">Reset</button>
Wrapping up
Now the application has the ability to add contact not just view them which is one step closer to what would be needed for a real application. The finished code can be found here.