Table of contents

< Previous chapterNext chapter >

Last updated .

Interacting with a server

The previous chapter gave an example of routing between a list of items and a detail view. It was pretty lame, though, because the data were hardcoded in the script files. Hardly a realistic scenario (I hope). So, this chapter is going to break the isolation on the client side and start communicating with a server over the wire. This will also give me the opportunity to make a real CRUD application with Angular: I want to build a small app that allows the user to maintain a catalog of contacts.

Unfortunately, this means I will have to diverge from the principle I stated from the outset of not requiring anything but Angular itself, for this time I am forced to create a service to communicate with. But fortunately I can do this with Visual Studio and to keep things as simple and lean as possible I will go for a ASP.Net WebAPI service. Although this is all about Angular, I will show what I did to create the service, just for completeness' sake. But since this series is not about .Net development, I will not delve too deeply . So, to get started, create a ASP.Net Web Application project in a new solution. I named it "ContactService". This project will host the new service:

Create a new Web project
Create a new Web project

In the next wizard step select the Empty option and tick the Web API box to get the references for ASP.Net Web API:

Make an empty Web API project
Make an empty Web API project

This gives me a project with just a few files. In the real world, one would hopefully go for some sort of database to store the data, but to keep dependencies and the number of technologies at a minimum, I will simply store the user's data in memory on the server. We will need just a single entity in our data model, so right-click the Models folder to create a new class called Contact with a few properties that one might imagine for such an entity:

Models/Contact.cs using System; using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; namespace ContactService.Models { public enum Gender { Unknown, Male, Female } public class Contact { public Guid ID { get; set; } [Required] public string Name { get; set; } [Required] [EmailAddress] public string Email { get; set; } public bool IsActive { get; set; } [JsonConverter(typeof(StringEnumConverter))] public Gender Gender { get; set; } } }
The new project
The new project

Just to have a variety of property types to play with, I chose to define the properties Name, Email, the boolean IsActive and Gender, which is typed to an enum of the same name. This will also give me the opportunity to play with a variation of input elements in the UI. I also added some validation to these properties, employing the validation types from the System.ComponentModel.DataAnnotations namespace. For the Gender property I added an attribute instructing the JsonConverter to accept string input ("Male" or "Female") and convert it to the proper enum (by default, Angular uses the json data format for communicating with a service).

We will also need a controller class to handle the HTTP requests from the Angular client side, so right-click the Controllers folder and add a new Controllers class. Before we can add any handler methods, let's deal with the primitive "database". I have set it up to keep the data in the server cache in a Dictionary<Guid, Contact> collection:

Controllers/ContactController.cs using System; using System.Collections.Generic; using System.Runtime.Caching; using System.Web.Http; using ContactService.Models; namespace ContactService.Controllers { [RoutePrefix("api/contact")] public class ContactController : ApiController { Dictionary<Guid, Contact> Data { get { return (Dictionary<Guid, Contact>) MemoryCache.Default.Get("Contacts"); } set { MemoryCache.Default.Set("Contacts", value, ObjectCache.InfiniteAbsoluteExpiration); } } public ContactController() { if (null == Data) Data = new Dictionary<Guid, Contact>(); } } }

You must set a reference to the System.Runtime.Caching assembly for the above code to compile. Notice also there is a route specification for the controller. Now, the CRUD operations can be implemented as methods on the controller. In keeping with REST principles, we'll make use of the various HTTP verbs. First, a couple of GET methods to get either one contact or all of them:

[Route("")] public IHttpActionResult Get() { return Ok(Data.Values); } [Route("{id}")] public IHttpActionResult Get(Guid id) { Contact item = null; if (!Data.TryGetValue(id, out item)) return NotFound(); return Ok(item); }

The Post method is for creating a new contact:

[HttpPost] [Route("")] public IHttpActionResult Post(Contact contact) { if (!ModelState.IsValid) return BadRequest(ModelState); contact.ID = Guid.NewGuid(); Data[contact.ID] = contact; return Ok(); }

The Put method is for updating an existing contact. The id comes from the Uri, while the contact object is taken from the message body:

[HttpPut] [Route("{id}")] public IHttpActionResult Put([FromUri] Guid id, [FromBody] Contact contact) { if (!ModelState.IsValid) return BadRequest(ModelState); Contact item = null; if (!Data.TryGetValue(id, out item)) return NotFound(); item.Name = contact.Name; item.Email = contact.Email; item.IsActive = contact.IsActive; item.Gender = contact.Gender; return Ok(); }

Finally we need a way to delete a contact:

[Route("{id}")] public IHttpActionResult Delete(Guid id) { Contact item = null; if (!Data.TryGetValue(id, out item)) return NotFound(); Data.Remove(id); return Ok(); }

These five methods go in the body of the ContactController class. I used the Postman (a Google Chrome plugin) tool to send requests to the service and verify that it works as expected. There is one annoying issue we have to deal with, though. When the service is running locally in IIS Express, it listens on a certain port. When the client application (which is yet to be constructed) is run, it is running on the same machine, but using a different port. This will prevent the communication between client and server due to the same origin security policy of web browsers. The workaround is to enable cross origin resource sharing in the service. It is pretty easy, thankfully. First, pull some necessary assemblies using this NuGet command in the Packet Manager Console: Install-Package Microsoft.AspNet.WebApi.Cors. Next, add the line config.EnableCors(new EnableCorsAttribute("*", "*", "*")) in the App_Start/WebApiConfig.cs file. This is the complete contents of that file:

App_Start/WebApiConfig.cs using System.Web.Http; using System.Web.Http.Cors; namespace ContactService { public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); config.EnableCors(new EnableCorsAttribute("*", "*", "*")); } } }

The service is now complete and ready to be called. I guess the next obvious step is to consider how we can call it from an Angular web app.

The Angular $http service

We have already touched on the topic of services in Angular. $http is a built-in service used to make http calls and it has this signature: $http(config). The single parameter is an object with a bunch of properties used to configure the request. The most important of these are:

By default, Angular uses json data format. Angular communicates this to the service by setting certain http headers in the request.

The request is asynchronous; the $http method returns a promise object with functions that will be called when the asynchronous call returns. An example:

var config = { method: "GET", url: "api/whatever" }; var promise = $http(config); promise.then( function (response) { // success! var data = response.data; }, function (response) { // handle error } );

The call to $http returns immediately, but the functions passed to promise will be called later when the asynchronous http call completes. It is not so important now what that promise object is and how it works. Suffice it to say here that you pass two function objects to its then function: The first function will be called if the request is successful whereas the second function will be called if the request is not successful, i.e. when an exception is thrown or the http request returns an http error code.

Angular also exposes a number of methods that wrap $http, corresponding to the common http verbs. For example, to send a GET request, the method of the same name can be called instead of calling $http directly:

var promise = $http.get("api/whatever"); promise.then( function (response) { // success! var data = response.data; }, function (response) { // handle error } );

Coding the client side

Armed with the knowledge of the $http service, I can now implement some javascript to make use of it by coding some requests to the service we just made with the WebAPI. Let's encapsulate the calls to our WebAPI service in an Angular proxy service. Back in the AngularProject Visual Studio project I added this implementation for that service:

Contact/ContactProxy.js ( function () { var app = angular.module("AngularProject"); app.service("contactProxy", ["$http", function ($http) { const baseUrl = "http://localhost:53833/api/"; this.GetContacts = function () { return $http.get(baseUrl + "contact"); }; this.GetContact = function (id) { return $http.get(baseUrl + "contact/" + id); }; this.CreateContact = function (contact) { return $http.post(baseUrl + "contact", contact); }; this.UpdateContact = function (id, contact) { return $http.put(baseUrl + "contact/" + id, contact); }; this.DeleteContact = function (id) { return $http.delete(baseUrl + "contact/" + id); }; } ]); } )();

The service constructor function declares a dependency on the $http service and thus receives an instance of that service. The new contactProxy service is just a thin wrapper around using the various $http methods and each method returns the promise object. Notice how the five methods mirror the five methods in the api. Now, a controller class can create a Contact object simply by calling the appropiate method in this service. Notice also that the server url is hardcoded. If you want to run it on your side, be sure to replace that url with whatever url your service runs on.

The controller class

So far, so good. Before presenting the controller class, I need to clarify what sort of UI I would like. I would like to make a table-like UI with a Contact per row and with in-place editing. Of course, only one row should be editable at a time. The last row should be for adding a new contact. Something like this:

Mockup
A mockup of the GUI

We are going to need a controller to form the bridge between the UI and the contactProxy service we just implemented. In contrast with what we have seen in the previous chapters, this time the data will come from an external (not hardcoded) source, which is nice. Other than that, the controller code looks a lot like the controllers we have seen up till now in its basic structure, only it's a little bigger, which is why I have sprinkled it with some comments that hopefully explain everything:

Contact/ContactController.js ( function () { var app = angular.module('AngularProject'); app.controller("contactController", ContactController, ["contactProxy"]); function ContactController(contactProxy) { var vm = this; // Data vm.Contacts = []; // The Contact being edited vm.EditContact = null; // Template object for making new Contact vm.ContactTemplate = { // set up default values IsActive: true, Gender: "Male", }; // Contact to be created vm.NewContact = {}; // Make new Contact from template angular.copy(vm.ContactTemplate, vm.NewContact); // Call to set in edit mode vm.SetEdit = function (contact) { vm.EditContact = contact; }; // Log service errors vm.ErrorHandler = function (response) { console.log("Ups - error!"); } // Get all Contacts vm.GetContacts = function () { contactProxy.GetContacts().then(function (response) { vm.Contacts = response.data; }, vm.ErrorHandler); }; // Create a new Contact vm.CreateContact = function () { contactProxy.CreateContact(vm.NewContact).then(function (response) { angular.copy(vm.ContactTemplate, vm.NewContact); vm.GetContacts(); }, vm.ErrorHandler); }; // Update existing Contact vm.UpdateContact = function () { contactProxy.UpdateContact(vm.EditContact.ID, vm.EditContact).then(function (response) { vm.EditContact = null; vm.GetContacts(); }, vm.ErrorHandler); }; // Delete Contact vm.DeleteContact = function (contact) { contactProxy.DeleteContact(contact.ID).then(function (response) { vm.GetContacts(); }, vm.ErrorHandler); }; // Gets whether a contact is being edited vm.IsEditing = function (contact) { if (vm.EditContact == null) return false; return vm.EditContact.ID === contact.ID; }; // Start by getting all contact objects vm.GetContacts(); } } )();

There are three properties available for data binding in the UI: The Contacts property is what the UI will enumerate when rendering the table of contact objects. The UI will bind to the EditContact when editing an existing object, and the NewContact will be bound to when creating a new Contact object. The SetEdit method is used for setting a particular object in edit mode and the IsEditing method can say if an object is being edited. Other than that, the controller "class" is pretty much a thin wrapper around the contactProxy we made before. The last line of code in the controller makes a call to GetContacts, thus ensuring that the data are retrieved right away.

The user interface

As outlined above, I envision a table-like UI with one Contact object per row and with in-place editing. When a row is being edited, there should be a Save button for persisting the data and a Cancel button to cancel the edit operation. Conversely, when a row is not being edited, the first button should be Edit to set the row in edit mode and the second button a Delete button to allow the user to delete an item.

Of course, validation should ensure that only valid data are persisted. In terms of the Contact class this means that Name and Email are specified and that the Email is valid.

Contacts.html <!doctype html> <html ng-app="AngularProject"> <head> <title>Home</title> <meta charset="utf-8" /> <script src="Scripts/Angular/angular-1.5.0-rc.0/angular.js"></script> <style> #table { display: table; } #table>form, #table>div { display: table-row; } #table>form>div, #table>div>div { display: table-cell; } #table input[type=text] { width: 96%; } #table input[type=email] { width: 96%; } #table button { width: 70px; } </style> </head> <body> <h1>The Contact List App</h1> <div id="table" ng-controller="contactController as vm"> <!--Headers--> <div style="font-weight:bold;"> <div style="width:200px;">Name</div><div style="width:200px;">Email</div> <div style="width:70px;">Active</div><div style="width:150px;">Gender</div> <div></div><div></div> </div> <!--Contact items--> <div ng-repeat="contact in vm.Contacts" ng-form="vm.formUpdate"> <div> <span ng-hide="vm.IsEditing(contact)">{{::contact.Name}}</span> <input ng-show="vm.IsEditing(contact)" ng-model="vm.EditContact.Name" type="text" required /> </div> <div> <span ng-hide="vm.IsEditing(contact)">{{::contact.Email}}</span> <input ng-show="vm.IsEditing(contact)" ng-model="vm.EditContact.Email" type="email" required /> </div> <div> <span ng-hide="vm.IsEditing(contact)">{{::contact.IsActive}}</span> <input ng-show="vm.IsEditing(contact)" ng-model="vm.EditContact.IsActive" type="checkbox"> </div> <div> <span ng-hide="vm.IsEditing(contact)">{{::contact.Gender}}</span> <div ng-show="vm.IsEditing(contact)"> <input ng-model="vm.EditContact.Gender" value="Male" type="radio">Male <input ng-model="vm.EditContact.Gender" value="Female" type="radio">Female </div> </div> <div> <button ng-hide="vm.IsEditing(contact)" ng-click="vm.SetEdit(contact)">Edit</button> <button ng-show="vm.IsEditing(contact)" ng-click="vm.UpdateContact()" ng-disabled="vm.formUpdate.$invalid">Save</button> </div> <div> <button ng-hide="vm.IsEditing(contact)" ng-click="vm.DeleteContact(contact)">Delete</button> <button ng-show="vm.IsEditing(contact)" ng-click="vm.SetEdit(null)">Cancel</button> </div> </div> <!--New Contact--> <div ng-form="vm.formCreate"> <div> <input ng-model="vm.NewContact.Name" type="text" required /> </div> <div> <input ng-model="vm.NewContact.Email" type="email" required /> </div> <div> <input ng-model="vm.NewContact.IsActive" type="checkbox"> </div> <div> <input ng-model="vm.NewContact.Gender" value="Male" type="radio">Male <input ng-model="vm.NewContact.Gender" value="Female" type="radio">Female </div> <div> <button ng-click="vm.CreateContact()" ng-disabled="vm.formCreate.$invalid">Create</button> </div> <div></div> </div> </div> <script src="App/App.js"></script> <script src="Contact/ContactProxy.js"></script> <script src="Contact/ContactController.js"></script> </body> </html>

Showing existing items

The section of the grid containing existing Contact items has its own form. Interestingly, there is no html form element; the ng-form directive takes its place and enables the use of validation. The display of a row depends on whether the underlying Contact object is being edited and is regulated by the use of ng-show and ng-hide directives:

Creating a new item

The last row contains the input controls for creating a new item. This section has its own ng-form instance.

Validation

I left out the visual prompting of the user to correct ínput errors, but the use of ng-disabled on the buttons ensures that invalid data are not persisted.

Styling

A minimum of styling is applied to produce a reasonable UI experience.

Now, the whole thing can be run to produce this user experience:

The completed application
The completed application

In order to run the app, first start the service by pressing F5 in the Visual Studio instance containing the service project. Then, in another Visual Studio instance with the client project, press F5 as well to launch the page in a browser. Initially the table of contacts will be empty. But they can be created (only to vanish when the service stops). I am sure you can spot a couple of improvement points which I encourage you to fix. One thing which is really nice about client-side web development is that you never recompile anything. Just make the file changes, hit F5 in the browser and that's it!

If you want to do some more experiments from here, you can download the source files. Make your own Visual Studio project to host them. Source files for the Web API service is here.

< Previous chapterNext : Dependency injection >


2016 by Niels Hede Pedersen Linked in