My previous post described the back-end configuration for my “People” domain model (people, addresses, etc.) Let’s look at how this data is used within the web application using an MVC web API controller along with an AngularJS web service call and simple data binding.
The Web API
I created a very simple ApiController class, called PeopleController, to start with.
public class PeopleController : ApiController { public IEnumerable<Person> Get() { var repository = new ExampleDataRepository(); return repository.GetSomePeople(); } public Person Get(int id) { var repository = new ExampleDataRepository(); var person = repository.GetPerson(id); if (person == null) { throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)); } return person; } }
We’ll add some more robust functionality later, but for now, this web service delivers either a list of people, or details about an individual person. The work to look up these domain entities in the database is delegated to a Repository class.
public class ExampleDataRepository { public IEnumerable<Person> GetSomePeople() { using (var ctx = new ExampleDbContext()) { return ctx.People.AsNoTracking().Take(30).ToList(); } } public Person GetPerson(int personId) { using (var ctx = new ExampleDbContext()) { return ctx .People .Include(p => p.EmailAddresses) .Include(p => p.PostalAddresses) .Include(p => p.PhoneNumbers) .AsNoTracking() .SingleOrDefault(p => p.PersonId == personId); } } }
Again, we are starting out simply at first. The GetSomePeople method returns the first 30 People records it finds. The GetPerson method returns the details of a particular person along with their associated information.
A few things to note about the preceding code are:
- The AsNoTracking method is used to disable tracking for these entities. This provides a performance improvement since the entities do not need to be tracked within the Entity Framework context (they are just immediately going out over the wire back to the browser).
- The Include method is used to include related entities (like email addresses). These additional related entities are not needed when getting a list of people – only when getting the details of a particular person.
- Notice that a new ExampleDbContext is created for each call to a repository method (this is one pattern — there are other patterns as well). The first time an application domain creates an instance of this context, a view is created of the data model and cached for later use. Therefore subsequent instantiations of the context use this cached view and can more quickly be created.
- If a person with a matching id cannot be found, the repository method just returns a null value. However, the web API then throws an HttpResponseException with an HttpStatusCode.NotFound (an HTTP “404 Not Found” error code). This is typically how REST APIs return error results.
AngularJS Simple Data Binding
The listCtrl.js file contains the controller functionality for displaying the list of people returned from the web service. When the controller is instantiated, there are three AngularJS services injected into it:
- $scope – this object contains the data that will be bound to the HTML view. In our case it will contain a people property that contains an array of people retrieved from the web service.
- $http – this service is used to make the call to the web service.
- $location – this service is used to navigate to a different view. In our case we will be navigating to the person detail view.
angular .module('myApp.ctrl.list', []) .controller('listCtrl', ['$scope', '$http', '$location', function ($scope, $http, $location) { $scope.people = []; $scope.viewPerson = function (id) { $location.path("/detail/" + id); }; $http({ method: 'GET', url: '/api/people' }).success(function (data, status, headers, config) { $scope.people = data; }); }]);
The $http service is used to make a call to the “/api/people” web service. It returns a promise that can be used to handle the result of the service call. In our case, we just update the $scope object with the new list of people.
AngularJS uses declarative markup to define how data is bound to the view. In other words, you modify the HTML content with special tags and attributes that the AngularJS engine then interprets to dynamically update the view.
For this example, our data binding is very simple.
<tbody> <tr ng-repeat="person in people"> <td><a ng-click="viewPerson(person.personId)"><i class="icon-edit"></i></a></td> <td>{{person.title}}</td> <td>{{person.firstName}}</td> <td>{{person.middleName}}</td> <td>{{person.lastName}}</td> <td>{{person.suffix}}</td> </tr> </tbody>
This markup repeats (“ng-repeat”) a table row (“<tr>”) for each person in the people collection (which is contained in the controller’s scope object). The double-braces (“{{ … }}”) syntax is used to bind data to the view. Finally, when the user clicks (“ng-click”) on the link, the viewPerson method is called on the scope, which in turns navigates to the person detail view.
AngularJS Two-Way Data Binding
For the detail view, the controller is similar to the list view controller.
angular .module('myApp.ctrl.detail', []) .controller('detailCtrl', ['$scope', '$http', '$routeParams', '$location', function ($scope, $http, $routeParams, $location) { $scope.person = { title: '', firstName: '', middleName: '', lastName: '', suffix: '' }; $scope.returnToList = function () { $location.path("/"); }; $http({ method: 'GET', url: '/api/people/' + $routeParams.id }).success(function (data, status, headers, config) { $scope.person = data; }); }]);
In this case, however, a call to the web service requires the id of the person to fetch. Therefore an additional service, $routeParams, is injected into the controller that can be used to obtain parameters associated with the route itself. Notice that the details route includes an “:id” parameter.
$routeProvider.when('/detail/:id', { templateUrl: '/Home/Detail', controller: 'detailCtrl', });
This parameter is added as a property of the $routeParams service and is used to modify the web service call.
The details view itself uses standard HTML input controls to display the person’s details.
Note that the input controls use the data-ng-model attributes (“data-ng-model” and “ng-model” are the same – it just depends if you want to use more strict HTML5 attributes). This incorporates two-way binding between the model contained within the controller’s scope and the HTML input controls. In other words, a change in the model will be reflected in the input control, and a change on the input control will be reflected in the model.
This example, of course, is only a simple read-only view of the people data. What’s more interesting is to incorporate additional interactive user interface elements, including displaying the data within a grid, editing people information, performing validation, etc. So, more to come …