Consume Web API with an Angularjs service

This topic concerns a problem that I faced in these days, that was the building of an Angularjs service to consume Web API’s, witch in my case were implemented with OData protocol.

The start point are some Web API’s, one for every entity of the project, that they responds to a specific url, for example:

I wouldn’t want to implement a service for every single Web API that do the same operations, so the idea is implement a generic javascript class with the basic methods and a factory that generates instances of this class; every module will have an own instance with some custom options, such as the service url.

So what I want to do are two things, a generic class that implements commons methods for CRUD operations and then a factory that generates instances of this service.

Generic service

Angularjs has several provider that we can use to instantiate services; the provider is the lower level and is use usually to configure reusable services that can be used in different applications.

Factory and services are syntactic sugar on top of a provider, and allow us to define singleton services in our application; the last two are constant and value that they provides values.

One of the main debates on Angularjs is when we use a factory or a service.

The main difference from these is that in the first one we needs to return an instance of the service in this way:


(function (window, angular) {
'use-strict'
angular.module('odataModule', [])
.factory('odataService', [function() {
returns {
.....
}
}]);
})(window, window.angular)

In the second one, we don’t need to do this, the angular service deal with object instantiation:


(function (window, angular) {
'use-strict'
angular.module('odataModule', [])
.service('odataService', [function() {
.....
}]);
})(window, window.angular)

What I want to implement is a javacript class that will have these properties/methods:

  • A url property, the Web API reference
  • An entityClass property, is the the javascript object that represents the entity to be managed
  • A isNew property, that allow the service to understand that the object need to be added (post) or saved (patch)
  • A getEntities method that retrieves the list of the entities
  • A createEntity method that returns a instance of a new entity
  • A getEntity method to retrieves a single entity
  • A saveEntity method that deal with the post/patch of the entity
  • And finally a method deleteEntity

An angular service help in this work by letting me write cleaner code that factory; a concrete implementation is:


(function (window, angular) {
'use-strict'
angular.module('odataModule', [])
.constant('blogsUrl', '/odata/Blogs')
.constant('postsUrl', '/odata/Posts')
.service('odataService', ['$http', function($http) {
function Instance(url, entityObject) {
this.isNew = false;
this.url = url;
this.entityClass = entityObject;
}

Instance.prototype.getEntities = function () {
return $http.get(this.url);
}

Instance.prototype.createEntity = function() {
this.isNew = true;
return angular.copy(this.entityClass);
}

Instance.prototype.getEntity = function (id) {
return $http.get(this.url + '(guid\'' + id + '\')');
}

Instance.prototype.saveEntity = function (entity) {
if (this.isNew) {
this.isNew = false;
return $http.post(this.url, entity);
}
else
return $http.patch(this.url + '(guid\'' + entity.Id + '\')', entity);
}

Instance.prototype.deleteEntity = function (entity) {
return $http.delete(this.url + '(guid\'' + entity.Id + '\')');
}
this.GetInstance = function(url, entityObject) {
return new Instance(url, entityObject);
};
}]);
})(window, window.angular)

The Instance class is the implementation of the common methods that we needed.

The last row is the main difference from a factory; in a service, the container function is a javascript function with a constructor and the service returns an instance of it, so I can define a method GetInstance that will returns an instance of my custom class.

If I had used a factory, the last rows would be the following:


return {
GetInstance: function(url, entityObject) {
return new Instance(url, entityObject);
}

Much better the implementation with the service.

Factories of the entities

Now I need to define a specific factory for every entity:


(function (window, angular) {
'use-scrict';
angular.module('blogsModule', ['ui.router', 'odataModule'])
.factory('blogsFactory', ['odataService', 'blogsUrl', function (odataService, blogsUrl) {
var blogEntity = { Name: '', Url: '' };
return odataService.GetInstance(blogsUrl, blogEntity);
}])
})(window, window.angular)

So far so good, I have an instance of the service with specific parameters, and now I can use the factory for crud operations in my controller:


(function (window, angular) {
'use-scrict';
angular.module('blogsModule', ['ui.router', 'odataModule'])
....
.controller('blogsController', ['$scope', '$state', 'blogsFactory', function ($scope, $state, blogsFactory) {
$scope.Title = 'Blogs';

blogsFactory.getEntities().then(function (result) {
$scope.blogs = result.data.value;
});

$scope.create = function() {
$state.go('main.blog', { id: null });
};

$scope.edit = function (blog) {
$state.go('main.blog', { id: blog.Id });
};
}])
.controller('blogController', ['$scope', '$state', '$stateParams', 'odataService', function ($scope, $state, $stateParams, odataService) {
$scope.Title = 'Blog';
var service = new odataService.GetInstance('/odata/Blogs', { Name: '', Url: '' });

$scope.save = function () {
service.saveEntity($scope.blog).then(function () {
$state.go('main.blogs');
});
};

$scope.delete = function() {
service.deleteEntity($scope.blog).then(function () {
$state.go('main.blogs');
});
};

$scope.close = function() {
$state.go('main.blogs');
};

if ($stateParams.id === '')
$scope.blog = service.createEntity();
else {
odataService.getEntity($stateParams.id).then(function (result) {
$scope.blog = result.data;
});
}
}]);
})(window, window.angular)

With few lines of code I have managed the crud operations.

In my case the odata module can be improved with checks about the entity validations, by using the $metadata informations returned by the odata services and I can retrieve also the entity fields without passing those as parameters, but, anyway, this is a good starting point.

 

 

 

 

Consume Web API with an Angularjs service

Manage tables data with AngularJS Part 1: tables metadata

During the deployment of an AngularJS app, we often develop controllers and views to manage data of basic tables,  such as zip, city, country and so on.

We need to offer to the users the CRUD operations of these tables; the functionalities and the structure of these controllers/view are very similar and you need to implement a lot of similar code to do that.

What you can do is implement the AngularJS directives that are able to show the content of basic tables and manage the CRUD operations.

The start point is a simple AngularJS SPA that consume ASP.NET Web API; the application using Entity Framework 6 code first and the only entity is city.

OData controller

Once the table is configured, what we need to do is implement an OData controller that will expose the CRUD operations of the Cities table and the metadata.

For this example we implement and endpoint that support OData Version 3:

public class CitiesController : ODataController
{
private Context db = new Context();

// GET: odata/Cities
[EnableQuery]
public IQueryable<City> GetCities()
{
return db.Cities;
}

// GET: odata/Cities(5)
[EnableQuery]
public SingleResult<City> GetCity([FromODataUri] Guid key)
{
return SingleResult.Create(db.Cities.Where(city => city.Id == key));
}

// PUT: odata/Cities(5)
public async Task<IHttpActionResult> Put([FromODataUri] Guid key, Delta<City> patch)
{
Validate(patch.GetEntity());

if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

City city = await db.Cities.FindAsync(key);
if (city == null)
{
return NotFound();
}

patch.Put(city);

try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CityExists(key))
{
return NotFound();
}
else
{
throw;
}
}

return Updated(city);
}

// POST: odata/Cities
public async Task<IHttpActionResult> Post(City city)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

db.Cities.Add(city);

try
{
await db.SaveChangesAsync();
}
catch (DbUpdateException)
{
if (CityExists(city.Id))
{
return Conflict();
}
else
{
throw;
}
}

return Created(city);
}

// PATCH: odata/Cities(5)
[AcceptVerbs("PATCH", "MERGE")]
public async Task<IHttpActionResult> Patch([FromODataUri] Guid key, Delta<City> patch)
{
Validate(patch.GetEntity());

if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

City city = await db.Cities.FindAsync(key);
if (city == null)
{
return NotFound();
}

patch.Patch(city);

try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CityExists(key))
{
return NotFound();
}
else
{
throw;
}
}

return Updated(city);
}

// DELETE: odata/Cities(5)
public async Task<IHttpActionResult> Delete([FromODataUri] Guid key)
{
City city = await db.Cities.FindAsync(key);
if (city == null)
{
return NotFound();
}

db.Cities.Remove(city);
await db.SaveChangesAsync();

return StatusCode(HttpStatusCode.NoContent);
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}

private bool CityExists(Guid key)
{
return db.Cities.Count(e => e.Id == key) > 0;
}
}

Then, you need to configure the OData Endpoint in the WebApiConfig.cs:

public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<City>("Cities");
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
}
}

OData controller is helpful to retrieve the metadata and you can using $metadata keyword:

http://host_name/odata/$metadata

We’ll receive a response like this:

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">
<edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<Schema Namespace="AngularTablesDataManager.DataLayer" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityType Name="City">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Guid" Nullable="false" />
<Property Name="Name" Type="Edm.String" />
</EntityType>
<EntityType Name="Zip">
<Key>
<PropertyRef Name="Id" />
</Key>
<Property Name="Id" Type="Edm.Guid" Nullable="false" />
<Property Name="Code" Type="Edm.Int16" Nullable="false" />
</EntityType>
</Schema>
<Schema Namespace="Default" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">
<EntityContainer Name="Container" m:IsDefaultEntityContainer="true">
<EntitySet Name="Cities" EntityType="AngularTablesDataManager.DataLayer.City" />
<EntitySet Name="Zips" EntityType="AngularTablesDataManager.DataLayer.Zip" />
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>

AngularJS service

The next step is the implementation of the AngularJS service that parse the metadata; first of all, a class that define the structure of metadata is required:

export class MetadataProperty {
Name: string;
Type: string;
Nullable: boolean;
}

Then, the MetadataService service:

export class MetadataService {
private $http: ng.IHttpService;
private $q: ng.IQService;

constructor($http: ng.IHttpService, $q: ng.IQService) {
this.$http = $http;
this.$q = $q;
}

public getMetadata(entityName: string): ng.IPromise<Array<models.MetadataProperty>> {
var defer: ng.IDeferred<any> = this.$q.defer();

var req = {
method: 'GET',
url: '/odata/$metadata'
};

this.$http(req).then(function (result) {
var data: string = result.data.toString();
var xmlDoc: XMLDocument = $.parseXML(data);
var xml: JQuery = $(xmlDoc);
var properties: Array<models.MetadataProperty> = new Array<models.MetadataProperty>();

xml.find('EntityType').find('Property').each(function () {
var metadataProperty: models.MetadataProperty = new models.MetadataProperty();
metadataProperty.Name = $(this).attr('Name');
metadataProperty.Type = $(this).attr('Type');
metadataProperty.Nullable = ($(this).attr('Nullable') != null) && ($(this).attr('Nullable').toLowerCase() == 'true');

properties.push(metadataProperty);
});

return defer.resolve(properties);
});

return defer.promise;
}

static factory() {
return ($http: ng.IHttpService, $q: ng.IQService) => new MetadataService($http, $q);
}
}

To consume the OData Controller you can use the $resource factory, that allows to perform CRUD operations easily, and extend the service with an additional method called getMetadata:

const entityName: string = 'Cities';
import ngr = ng.resource;
import commons = AngularTablesDataManagerApp.Commons;
import models = AngularTablesDataManagerApp.Models;
import services = AngularTablesDataManagerApp.Services;

export interface ICitiesResourceClass extends ngr.IResourceClass<ngr.IResource<models.ICity>> {
create(order: models.ICity): ngr.IResource<models.ICity>;
}

export class CitiesService implements models.IService {
private resource: ICitiesResourceClass;
private $q: ng.IQService;
private metadataService: services.MetadataService;

constructor($resource: ngr.IResourceService, $q: ng.IQService, MetadataService: services.MetadataService) {
this.$q = $q;
this.metadataService = MetadataService;

this.resource = <ICitiesResourceClass>$resource('/odata/' + entityName + '/:id', { id: '@Id' }, {
get: { method: "GET" },
create: { method: "POST" },
save: { method: "PUT" },
query: { method: "GET", isArray: false },
delete: { method: "DELETE" }
});
}

public create(order: models.ICity) {
return this.resource.create(order);
}

public save(order: models.ICity) {
if (order.Id == commons.Constants.GuidEmpty) {
return this.resource.create(order);
}
else {
return this.resource.save(order);
}
}

public delete(order: models.ICity) {
return this.resource.remove(order);
}

public getAll() {
var datas: ngr.IResourceArray<ngr.IResource<models.ICity>>
var defer: ng.IDeferred<any> = this.$q.defer();

this.resource.query().$promise.then((data: any) => {
datas = data["value"];

return defer.resolve(datas);
}, (error) => {
return defer.reject(datas);
});

return defer.promise;
}

public getMetadata(): ng.IPromise<Array<models.MetadataProperty>> {
return this.metadataService.getMetadata(entityName);
}

static factory() {
return (r: ngr.IResourceService, $q: ng.IQService, MetadataService: services.MetadataService) => new CitiesService(r, $q, MetadataService);
}
}

This method can be called from a controller; we’ll obtain an array of MetadataProperty objects that represents the structure of the Cities table.

import ngr = ng.resource;
import commons = AngularTablesDataManagerApp.Commons;
import models = AngularTablesDataManagerApp.Models;
import services = AngularTablesDataManagerApp.Services;

export class CitiesController {
cities: ngr.IResourceArray<ngr.IResource<models.ICity>>;
city: models.ICity;
title: string;
toaster: ngtoaster.IToasterService;

private citiesService: services.CitiesService;
private constant: commons.Constants;
private metadataProperties: Array<models.MetadataProperty>;

constructor(toaster: ngtoaster.IToasterService, CitiesService: services.CitiesService) {
this.citiesService = CitiesService;
this.constant = commons.Constants;

this.toaster = toaster;
this.title = 'Cities';

this.Load();
}

private Load() {
this.citiesService.getMetadata().then((data) => {
this.metadataProperties = data;
}, (error) => {
this.toaster.error('Error loading cities medatad', error.data.message);
});

this.citiesService.getAll().then((data) => {
this.cities = data;
this.toaster.success('Cities loaded successfully.');
return;
}, (error) => {
this.toaster.error('Error loading cities', error.message);
});
}
}

AngularTablesDataManager.module.controller('CitiesController', CitiesController);

 

You can find the source code here.

 

Manage tables data with AngularJS Part 1: tables metadata