Consume Web API OData with ODataAngularResources

One month ago, I wrote this post about the odata services and how we can consume those with the $http factory of Angularjs.

This approach is not wrong but is not smart at all, because we have to write every query for any call that the application needs to do, and is not flexible.

What we could do is write our custom library that deal with the odata url format and it expose some fluent methods that the components of the application can call; in this way we have a layer between the angular services and the odata controller and the code should be more readable.

Anyway, write a library like that is not very easy and require a lot of amount of time.

The solution of these problems is a library already implemented that we can use directly in our angular application, that is ODataAngularResources.

This is a lightweight library that allow writing the OData queries with fluent methods.

What I’ll go to do is leverage this library for the queries and implement a custom service to manage the entities saving.

ODataResource factory

The first factory that I want to implement is an object that wrap the basic ODataResources module and expose the CRUD operations of the entity.

First of all, I need to install the library, and you can refer to the GitHub project for any detail.

Than, I can start with the implementation and creating the new module:


(function(window, angular) {
'use-strict';
angular.module('odataResourcesModule', ['ui.router', 'ODataResources'])

.factory('odataResource', function ($odataresource, $http, $q) {

function odataResource(serviceRootUrl, resourcePath, key) {
this.serviceRootUrl = serviceRootUrl;
this.resourcePath = resourcePath;
this.key = key;

this._odataResource = $odataresource(serviceRootUrl + '/' + resourcePath, {}, {}, {
odatakey: key,
isodatav4: true
});

angular.extend(this._odataResource.prototype, {
'$patch': function () {
var defer = $q.defer();

var req = {
method: 'PATCH',
url: serviceRootUrl + '/' + resourcePath + '(' + this.Id + ')',
data: this
};

$http(req).then(function (data) {
defer.resolve(data);
}, function (error) {
defer.reject(error);
});

return defer.promise;
}
});
}

.....

The factory has a constructor where I pass three parameters, the ServiceRootUrl that is the first part of the url (in my case odata), the resourcePath that identify the entity (Blogs) and the key of the table.

Then I configure the $odataresource with these parameters and I setup the odata v4 as well.

At the end I extend the OdataResource prototype because this library doesn’t provide the implementation for the PATCH verb, I need to implement it with my custom function.

So far so good, now I want to expose some methods to queries the resource or retrieve a single entity:


odataResource.prototype.getResource = function () {
return this._odataResource.odata();
}

odataResource.prototype.get = function (id) {
var defer = $q.defer();

this._odataResource.odata().get(id, function (data) {
data._originalResource = angular.copy(data);
defer.resolve(data);
}, function (error) {
defer.reject(error);
});

return defer.promise;
}

With the getResource method I implement a getter for the resource and allow the caller to execute the queries.

The get method deserves a particular explanation, once the single entity is retrieved, I make copy of the original entity and assign that to a new private property named _originalResource.

This will allow me two check the values that will be changed on the entity, and compose the object that will be sent with the patch method.

Now I can implement the others methods to add, update and delete the entity:


odataResource.prototype.new = function () {
return new this._odataResource();
}

odataResource.prototype.add = function (resource) {
var defer = $q.defer();

resource.$save(function (data) {
data._originalResource = angular.copy(data);
defer.resolve(data);
}, function (error) {
defer.reject(error);
});

return defer.promise;
}

odataResource.prototype.update = function (resource) {
var defer = $q.defer();

var self = this;
resource.$patch().then(function () {
self.get(resource[self.key]).then(function (data) {
defer.resolve(data);
});
}, function (error) {
defer.reject(error);
});

return defer.promise;
}

odataResource.prototype.delete = function (resource) {
var defer = $q.defer();

resource.$delete(function (data) {
defer.resolve(data);
}, function (error) {
defer.reject(error);
});

return defer.promise;
}

Like the get method, I do a copy of the entity in the add method as well.

Now I have a factory that is able to query Web API OData services and manage the crud operations and I can proceed with some business logic.

ODataGenericResource factory

The first step is define the new factory:


.factory('odataGenericResource', function ($q, odataResource) {

function odataGenericResource(serviceRootUrl, resourcePath, key) {
this.odataResource = new odataResource(serviceRootUrl, resourcePath, key);
}

In the constructor I create a new instance of the ODataResource factory, in order to leverage the methods implemented above.

Based on the id parameter, now I implement a get method that will create a new resource or perform a get from a Web API OData service:


odataGenericResource.prototype.get = function (id) {
if (id === '') {
var defer = $q.defer();
defer.resolve(this.odataResource.new());

return defer.promise;
} else {
return this.odataResource.get(id);
}
}

If an external service will call this method with an empty id will receive a new resource, otherwise will receive the entity, if exists.

Now I need to implement the save method:


odataGenericResource.prototype.isChanged = function (resource) {
var isChanged = false;
for (var propertyName in resource) {
if (isEntityProperty(propertyName) && resource._originalResource[propertyName] !== resource[propertyName]) {
isChanged = true;
}
}

return isChanged;
}

odataGenericResource.prototype.getObjectToUpdate = function (resource) {
var object = this.odataResource.new();
object[this.odataResource.key] = resource[this.odataResource.key];

for (var propertyName in resource) {
if (isEntityProperty(propertyName) && resource._originalResource[propertyName] !== resource[propertyName]) {
object[propertyName] = resource[propertyName];
}
}

return object;
}

odataGenericResource.prototype.save = function(resource) {
if (!resource._originalResource) {
return this.odataResource.add(resource);
} else if (this.isChanged(resource)) {
var object = this.getObjectToUpdate(resource);
return this.odataResource.update(object);
} else {
var defer = $q.defer();
defer.resolve(resource);

return defer.promise;
}
}

I need some methods, to check the state of the entity.

The IsChanged method check if any property of the entity is changed, leveraging the _originalResource property.

The second method prepare the effective object to save, with only the properties that has been changed.

The save method check if the entity is new (it doesn’t have the _originalResource property) or not; based on that, it will be added or saved.

The delete method doesn’t have particular stuff and we can now take a look at the using of this factory in the application.

Application

I can use this factory in any angular module that needs to manage crud operations for an entity.

For example, in a blogsModule, I can define a factory like this:


(function (window, angular) {
'use-strict';
angular.module('blogsModule', ['ui.router', 'odataResourcesModule'])
.factory('blogsService', function ($http, odataGenericResource) {
return new odataGenericResource('odata', 'Blogs', 'Id');
})

And now in the controller:


.controller('blogsCtrl', function ($scope, $state, blogsService) {

$scope.new = function() {
$state.go("home.blog", { id: null });
};

$scope.detail = function(id) {
$state.go("home.blog", { id: id });
};

$scope.Blogs = blogsService.getOdataResource().query();
})
.controller('blogsDetailCtrl', function ($scope, $state, blogsService) {
var load = function (id) {
blogsService.get(id).then(function(data) {
$scope.Blog = data;
});
};

$scope.save = function () {
blogsService.save($scope.Blog).then(function(data) {
load(data.Id);
});
}

$scope.delete = function () {
blogsService.delete($scope.Blog).then(function () {
$scope.close();
});
};

$scope.close = function () {
$state.go("home.blogs");
};

load($state.params.id);
});

With few lines of code I can manage the calls to a Web API OData service, with the possibility do fluent odata queries.

You can find the source code here.

Advertisements
Consume Web API OData with ODataAngularResources

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 3: configuring the fields typologies

The last part of this argument is configuring the field typologies of the tables to manage.

What you need to do is specify for one or more fields of the table a couple of informations, like the typology of the field (text, number, radio, dropdown) and perhaps a list of values.

Also we might want to pass to the field external values instead of predefined values.

In order to do that and to improve the functionalities of the application, you need to implement some new features.

FieldConfiguration table

The first step is adding a new field configuration table:

[Table("FieldConfigurations")]
public class FieldConfiguration
{
public Guid Id { get; set; }
[Required]
public string Entity { get; set; }
[Required]
public string Field { get; set; }
[Required]
public string Tipology { get; set; }
public string Values { get; set; }
}

With this table we can specify for a field the tipology (text, number, radio, dropdown) and an optional list of values.

We need also update the database and implement a Web API to retrieve the datas from the new table:

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

// GET: odata/FieldConfigurations
[EnableQuery]
public IQueryable<FieldConfiguration> GetFieldConfigurations()
{
return db.FieldConfigurations;
}

// GET: odata/FieldConfigurations(5)
[EnableQuery]
public SingleResult<FieldConfiguration> GetFieldConfiguration([FromODataUri] Guid key)
{
return SingleResult.Create(db.FieldConfigurations.Where(fieldConfiguration => fieldConfiguration.Id == key));
}

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

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

FieldConfiguration fieldConfiguration = await db.FieldConfigurations.FindAsync(key);
if (fieldConfiguration == null)
{
return NotFound();
}

patch.Put(fieldConfiguration);

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

return Updated(fieldConfiguration);
}

// POST: odata/FieldConfigurations
public async Task<IHttpActionResult> Post(FieldConfiguration fieldConfiguration)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

fieldConfiguration.Id = Guid.NewGuid();
db.FieldConfigurations.Add(fieldConfiguration);

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

return Created(fieldConfiguration);
}

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

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

FieldConfiguration fieldConfiguration = await db.FieldConfigurations.FindAsync(key);
if (fieldConfiguration == null)
{
return NotFound();
}

patch.Patch(fieldConfiguration);

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

return Updated(fieldConfiguration);
}

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

db.FieldConfigurations.Remove(fieldConfiguration);
await db.SaveChangesAsync();

return StatusCode(HttpStatusCode.NoContent);
}

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

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

For this implementation only reading operations are needed, but still leave the other methods.

FieldConfigurations service

We need an Angular service to consume the Web API and we use the Angular $resource module.

In addition to the CRUD methods we implement additional methods like getField, getFieldConfigurationTipology and getFieldValues, that will help to retrieve specific informations about a field.

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

export interface IFieldConfigurationsResourceClass extends ngr.IResourceClass<ngr.IResource<models.IFieldConfiguration>> {
create(zip: models.IFieldConfiguration): ngr.IResource<models.IFieldConfiguration>;
}

export class FieldConfigurationsService {
private resource: IFieldConfigurationsResourceClass;
private $q: ng.IQService;
private $filter: ng.IFilterService;
private metadataService: services.MetadataService;
private entitySet: string = 'FieldConfigurations';
private fieldConfigurations: Array<models.IFieldConfiguration>;

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

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

public create(fieldConfiguration: models.IFieldConfiguration) {
return this.resource.create(fieldConfiguration);
}

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

public delete(fieldConfiguration: models.IFieldConfiguration) {
return this.resource.delete({ key: fieldConfiguration.Id });
}

public getFieldConfigurationTipology(entityName: string, fieldName: string): ng.IPromise<string> {
var defer: ng.IDeferred<string> = this.$q.defer();

this.getFieldConfiguration(entityName, fieldName).then((data: models.IFieldConfiguration) => {
if (data != null) {
defer.resolve(data.Tipology);
}
else {
defer.resolve('');
}
}, (error) => {
defer.reject(error);
});

return defer.promise;
}

public getFieldValues(entityName: string, fieldName: string): ng.IPromise<Array<string>> {
var defer: ng.IDeferred<Array<string>> = this.$q.defer();
var vm = this;

this.getFieldConfiguration(entityName, fieldName).then((data: models.IFieldConfiguration) => {
if (data != null && data.Values != null && data.Values != "") {
defer.resolve(data.Values.split(";"));
}
else {
defer.resolve(new Array<string>());
}

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

if (this.fieldConfigurations) {

}
else {

}

return defer.promise;
}

private getFieldConfiguration(entityName: string, fieldName: string): ng.IPromise<models.IFieldConfiguration> {
var defer: ng.IDeferred<models.IFieldConfiguration> = this.$q.defer();
var vm = this;

if (this.fieldConfigurations == null) {
vm.resource.query().$promise.then((data: any) => {
vm.fieldConfigurations = data["value"];
defer.resolve(vm.getField(entityName, fieldName));
}, (error) => {
defer.reject(error);
});
}
else {
defer.resolve(vm.getField(entityName, fieldName));
}

return defer.promise;
}

private getField(entityName: string, fieldName: string): models.IFieldConfiguration {
var fieldConfigurations: Array<models.IFieldConfiguration> = this.$filter('filter')(this.fieldConfigurations, { 'Entity': entityName, 'Field': fieldName }, true);
if (fieldConfigurations.length > 0) {
return fieldConfigurations[0];
}
else {
return null;
}
}

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

AngularTablesDataManager.module.factory('FieldConfigurationsService', ['$resource', '$q', '$filter', 'MetadataService', FieldConfigurationsService.factory()]);
}

The getFieldConfiguration method check if the configurations aren’t already loaded and if not invoke the Web API.

Field directive

Now you can implement the directive that will manage the fields visualization.

The directive accept some parameters, like the tipology of the field, the property object that you want to show and the possible values of the field:

module AngularTablesDataManagerApp.Directives {
import models = AngularTablesDataManagerApp.Models;
import services = AngularTablesDataManagerApp.Services;

interface IFieldDirectiveScope extends ng.IScope {
entityName: string;
contentUrl: string;
tipology: models.MetadataProperty;
property: models.RowProperty;
fieldItems: Array<models.FieldItem>;
values: Array<models.FieldItem>;
}

export class FieldDirective implements ng.IDirective {
fieldConfigurationsService: services.FieldConfigurationsService;
public restrict = 'E';
public scope = {
entityName: '=',
tipology: '=',
property: '=',
fieldItems: '='
};
public template = '<ng-include src="contentUrl" />';
public link = (scope: IFieldDirectiveScope, element: JQuery, attrs: IArguments) => {
scope.values = new Array<models.FieldItem>();

if (scope.fieldItems != null) {
for (var i = 0; i < scope.fieldItems.length; i++) {
scope.values.push(scope.fieldItems[i]);
}
}

var tipology: string = scope.tipology.Type;
this.fieldConfigurationsService.getFieldConfigurationTipology(scope.entityName, scope.tipology.Name).then((data: string) => {
if (data != "") {
tipology = data;
this.fieldConfigurationsService.getFieldValues(scope.entityName, scope.tipology.Name).then((data: Array<string>) => {
for (var i = 0; i < data.length; i++) {
var item: models.FieldItem = new models.FieldItem(data[i], data[i]);
scope.values.push(item);
}

this.openView(scope, tipology);
});
}
else {
this.openView(scope, tipology);
}
});
}

public constructor(fieldConfigurationService: services.FieldConfigurationsService) {
this.fieldConfigurationsService = fieldConfigurationService;
}

private openView(scope: IFieldDirectiveScope, tipology: string) {
if (tipology.toLowerCase().indexOf('int') != -1) {
scope.contentUrl = 'app/directives/InputNumber.html';
}
else if (tipology.toLowerCase().indexOf('radio') != -1) {
scope.contentUrl = 'app/directives/InputRadio.html'
}
else if (tipology.toLowerCase().indexOf('select') != -1) {
scope.contentUrl = 'app/directives/InputSelect.html';
}
else {
scope.contentUrl = 'app/directives/InputText.html';
}
}
}

AngularTablesDataManager.module.directive('field', ['FieldConfigurationsService', (fieldConfigurationService: services.FieldConfigurationsService) => new Directives.FieldDirective(fieldConfigurationService)]);
}

In the link function you need to call the service to retrieve the tipology of the field and to check if some values are defined.

An openView method check the field tipology and open the specific view; for example, the InputSelect view will look like this:

<select class="form-control" ng-model="property.Value">
<option ng-repeat="value in values | orderBy: 'Value'" value="{{value.Id}}" ng-selected="value.Id == property.Value">{{value.Value}}</option>
</select>

 

Grid directive

You need to extend the GridController and adding some methods to retrive the necessary informations and pass that to the Field directive:

interface IGridDirectiveScope extends ng.IScope {
entityName: string;
list: models.Grid;
item: models.Row;
rowModel: models.Row;
newItem: boolean;
fieldItems: Array<models.FieldItems>;

New(): void;
Save(item: IGridItem): void;
Delete(item: IGridItem): void;
Close(): void;
GetEntityName(): string;
GetFieldItems(fieldName: string): Array<models.FieldItem>;
IsVisiblePropertyInGrid(property: models.RowProperty): boolean;
IsVisiblePropertyInDetail(fieldName: string): boolean;
}

class GridController {
$scope: IGridDirectiveScope;
$filter: ng.IFilterService;

constructor($scope: IGridDirectiveScope, $filter: ng.IFilterService) {
this.$scope = $scope;
this.$filter = $filter;
}

public Edit(item: models.Row) {
this.$scope.item = item;
this.$scope.newItem = false;
}

public New() {
this.$scope.item = new models.Row(angular.copy(this.$scope.rowModel.Entity), this.$scope.rowModel.Name, angular.copy(this.$scope.rowModel.Properties));
this.$scope.newItem = true;
}

public Save(item: models.Row) {
var obj: IGridItem = { item: item };

this.$scope.Save(obj);
}

public Delete(item: models.Row) {
this.$scope.item = null;
var obj: IGridItem = { item: item };

this.$scope.Delete(obj);
}

public Close() {
if (!this.$scope.newItem) {
for (var i = 0; i < this.$scope.item.Properties.length; i++) {
this.$scope.item.Properties[i].Value = (<any>this.$scope.item.Entity)[this.$scope.item.Properties[i].Name];
}
}

this.$scope.item = null;
}

public GetEntityName() {
return this.$scope.entityName;
}

public GetFieldItems(fieldName: string) {
if (this.$filter('filter')(this.$scope.fieldItems, { 'FieldName': fieldName }, true).length > 0) {
return this.$filter('filter')(this.$scope.fieldItems, { 'FieldName': fieldName }, true)[0].FieldItems;
}
else {
return null;
}
}

public IsVisiblePropertyInGrid(property: models.RowProperty) {
return this.$filter('filter')(this.$scope.list.Columns, { 'Name': property.Name }, true)[0].ShowedInGrid;
}

public IsVisiblePropertyInDetail(property: models.RowProperty) {
return this.$filter('filter')(this.$scope.list.Columns, { 'Name': property.Name }, true)[0].ShowedInDetail;
}
}

A particular aspect is the fieldItems property, that is an array of objects; if you want to pass an array of values to the field directive, what we need to do is load these values in the caller of the directive and pass these as a parameter.

Now you need to change the GridItem view and use the FieldDirective:

<div class="row bootstrap-admin-no-edges-padding" style="margin-top: 10px;">
<div class="col-md-12">
<input type="button" id="btn-save" value="Save" class="btn btn-primary" ng-click="Save()" ng-disabled="form.$invalid" />
<input type="button" id="btn-delete" value="Delete" class="btn btn-danger" ng-click="Delete()" ng-show="!isNew" />
<input type="button" id="btn-close" value="Close" class="btn btn-default" ng-click="Close()" /></div>
</div>
<div class="row bootstrap-admin-no-edges-padding" style="margin-top: 10px;">
<div class="col-md-12">
<form name="form" role="form">
<div class="panel panel-default">
<div class="panel-heading">
<div class="text-muted bootstrap-admin-box-title">
{{item.Name}}</div>
</div>
<div class="panel-body">
<div class="form-group" ng-repeat="property in item.Properties | filter: IsVisibleProperty">
<label for="{{property.Name}}">{{property.Name}}:</label>
<field entity-name="GetEntityName()" tipology="GetMetadataProperty(property.Name)" property="property" field-items="GetFieldItems(property.Name)" /></div>
</div>
<div class="col-md-2"></div>
</div>
</form></div>
</div>

Cities controller

The last step is changing the main controller to retrive the new parameters that the GridDirective needed:

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

export class CitiesController {
entityName: string;
grid: models.Grid;
fieldItems: Array<models.FieldItems>;
rowModel: models.Row;
toaster: ngtoaster.IToasterService;

private citiesService: services.CitiesService;
private zipsService: services.ZipsService;
private constant: commons.Constants;

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

this.grid = new models.Grid();
this.fieldItems = new Array<models.FieldItems>();
this.grid.Title = 'Cities';
this.Load();
}

private Load() {
var columns: Array<models.Column> = new Array<models.Column>();
var column: models.Column = new models.Column('Name', true, true);
columns.push(column);
column = new models.Column('IdZip', false, true);
columns.push(column);
var vm = this;

this.zipsService.getAll().then((data) => {
var zips: models.FieldItems = new models.FieldItems('IdZip');

for (var i = 0; i < data.length; i++) {
zips.FieldItems.push(new models.FieldItem(data[i].Id, data[i].Code.toString()));
}

vm.fieldItems.push(zips);

vm.citiesService.getMetadata(columns).then((data) => {
vm.grid.Columns = data;
vm.rowModel = this.citiesService.createGridData(data);

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

}, (error) => {
vm.toaster.error('Error loading cities metadata', error.data.message);
});

}, (error) => {
vm.toaster.error('Error loading zips metadata', error.data.message);
});

}

public Save(item: models.Row) {
var vm = this;
var isNew: boolean = false;

if (item.Entity.Id == commons.Constants.GuidEmpty)
isNew = true;

this.citiesService.saveGridData(item).then((data: models.Row) => {
if (isNew)
vm.grid.Rows.push(data);

this.toaster.success("City saved successfully.");
}, (error: any) => {
this.toaster.error("Error saving city", error.data.message);
});
}

public Delete(item: models.Row) {
var vm = this;
this.citiesService.deleteGridData(item).then((data: any) => {
var index = vm.grid.Rows.indexOf(item);
vm.grid.Rows.splice(index, 1);

this.toaster.success("City deleted successfully.");
}, (error: any) => {
this.toaster.error("Error deleting city", error.data.message);
});
}
}

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

The items to pass to the GridController are an array of FieldItems; an object FieldItems has a FieldName that is the field to witch to associate the values and an array of FieldItem that are the effective values.

Using the ZipService we build an array of FieldItem associated to the IdZip field, and we’re passing that to the GridDirective.

The view is modified like this:

<grid list="vm.grid" entity-name="vm.entityName" row-model="vm.rowModel" order="Name" save="vm.Save(item)" delete="vm.Delete(item)" field-items="vm.fieldItems"></grid>

 

You can find the source code here.

 

 

Manage tables data with AngularJS Part 3: configuring the fields typologies

Manage tables data with AngularJS Part 2: nested directives

With the Angular directives you can build reusable components, witch can accept parameters and scope variables, and witch can have customized behaviours.

It can be nested and communicate between them with functions and parameters.

We can using the directives for implement a dynamic HTML table grid, with dynamics rows and columns.

Grid model

In order to display the content, a class model is needed.

This model need to have the list of columns and the row elements:

import models = AngularTablesDataManagerApp.Models;

export class Grid {
Title: string;
Columns: Array&lt;MetadataProperty&gt;;
Rows: Array&lt;Row&gt;;
}

export class Row {
Entity: models.IEntity;
Name: string;
Properties: Array&lt;RowProperty&gt;;

constructor(entity: models.IEntity, name: string, datas: Array&lt;RowProperty&gt;) {
this.Entity = entity;
this.Name = name;
this.Properties = datas;
}
}

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

export class RowProperty {
Name: string;
Value: string;
Nullable: boolean;

constructor(name: string, value: string, nullable: boolean) {
this.Name = name;
this.Value = value;
this.Nullable = nullable;
}
}

A column is of type MetadataProperty, and have a name, a type and a nullable property.

A row is composed by the entity binded to the row, the name of the entity and an array of RowProperty; these properties contains the values of the fields, witch will be displayed in the grid.

Grid directive

The next step is the main Grid directive; the first element to define is the scope, that have the list, some other properties and the definition of the CRUD methods:

interface IGridItem {
item: models.Row;
}

interface IGridDirectiveScope extends ng.IScope {
list: models.Grid;
item: models.Row;
rowModel: models.Row;
newItem: boolean;

New(): void;
Save(item: IGridItem): void;
Delete(item: IGridItem): void;
Close(): void;
}

The implementation of the CRUD methods is in the grid directive controller:

class GridController {
$scope: IGridDirectiveScope;

constructor($scope: IGridDirectiveScope) {
this.$scope = $scope;
}

public Edit(item: models.Row) {
this.$scope.item = item;
this.$scope.newItem = false;
}

public New() {
this.$scope.item = new models.Row(angular.copy(this.$scope.rowModel.Entity), this.$scope.rowModel.Name, angular.copy(this.$scope.rowModel.Properties));
this.$scope.newItem = true;
}

public Save(item: models.Row) {
var obj: IGridItem = { item: item };

this.$scope.Save(obj);
}

public Delete(item: models.Row) {
this.$scope.item = null;
var obj: IGridItem = { item: item };

this.$scope.Delete(obj);
}

public Close() {
if (!this.$scope.newItem) {
for (var i = 0; i &lt; this.$scope.item.Properties.length; i++) {
this.$scope.item.Properties[i].Value = (&lt;any&gt;this.$scope.item.Entity)[this.$scope.item.Properties[i].Name];
}
}

this.$scope.item = null;
}
}

export class GridDirective implements ng.IDirective {
public restrict = 'E';
public templateUrl = 'app/directives/grid.html';
public scope = {
list: '=',
rowModel: '=',
order: '@order',
New: '&amp;new',
Save: '&amp;save',
Delete: '&amp;delete'
};
public controller = GridController;
}

The grid directive scope accept the list of the objects to show, the row model and the order of the grid; it also accept the references of the CRUD methods, because we’ll need to add the business logic; the & character means that we want to pass a parameter to the expression defined in the attribute.

In our case, we want to pass the object selected to the function.

Nested directives

The grid directive will hold the other directives necessary to render the grid:

interface IGridListDirectiveScope extends ng.IScope {
gridItem: models.Row;
New(): void;
}

export class GridListDirective implements ng.IDirective {
public require = '^grid';
public restrict = 'E';
public templateUrl = 'app/directives/gridList.html';
public scope = {
list: '=',
order: '='
};
public link = (scope: IGridListDirectiveScope, element: ng.IAugmentedJQuery, attrs: IArguments, gridCtrl: GridController) =&gt; {
scope.New = () =&gt; {
gridCtrl.New();
}
}
}

export class GridColumnDirective implements ng.IDirective {
public restrict = 'A';
public templateUrl = 'app/directives/gridColumn.html';
public scope = {
gridColumn: '='
};
}

interface IGridRowDirectiveScope extends ng.IScope {
gridRow: models.Row;
Edit(): void;
}

export class GridRowDirective implements ng.IDirective {
public require = '^grid';
public restrict = 'A';
public templateUrl = 'app/directives/gridRow.html';
public scope = {
gridRow: '='
};
public link = (scope: IGridRowDirectiveScope, element: ng.IAugmentedJQuery, attrs: IArguments, gridCtrl: GridController) =&gt; {
scope.Edit = () =&gt; {
gridCtrl.Edit(scope.gridRow);
}
}
}

export class GridCellDirective implements ng.IDirective {
public restrict = 'A';
public templateUrl = 'app/directives/gridCell.html';
public scope = {
gridCell: '='
};
}

interface IGridItemDirectiveScope extends ng.IScope {
item: models.Row;
metadata: Array&lt;models.MetadataProperty&gt;;
isNew: boolean;

GetMetadataProperty(Name: string): models.MetadataProperty;
Save(): void;
Delete(): void;
Close(): void;
}

export class GridItemDirective implements ng.IDirective {
$filter: ng.IFilterService;
public require = '^grid';
public restrict = 'E';
public templateUrl = 'app/directives/gridItem.html';
public scope = {
item: '=',
metadata: '=',
isNew: '='
};
public link = (scope: IGridItemDirectiveScope, element: ng.IAugmentedJQuery, attrs: IArguments, gridCtrl: GridController) =&gt; {
scope.Save = () =&gt; {
gridCtrl.Save(scope.item);
}

scope.Delete = () =&gt; {
gridCtrl.Delete(scope.item);
}

scope.Close = () =&gt; {
gridCtrl.Close();
}

scope.GetMetadataProperty = (Name: string) =&gt; {
return this.$filter('filter')(scope.metadata, { 'Name': Name })[0];
}
}

public constructor($filter: ng.IFilterService) {
this.$filter = $filter;
}
}

The require attribute with ^ character means that the directive searches for the controller on its own element or its parent.

So, in the link function of the directive you can call a function in the controller of the directive specified in the require attribute.

Grid directives views

The implementation of the views is simple; the first view is the grid:

&lt;div ng-show=&quot;item == null&quot;&gt;
&lt;grid-list list=&quot;list&quot; order=&quot;order&quot;&gt;&lt;/grid-list&gt;
&lt;/div&gt;
&lt;div ng-show=&quot;item != null&quot;&gt;
&lt;grid-item item=&quot;item&quot; metadata=&quot;list.Columns&quot; is-new=&quot;newItem&quot;&gt;&lt;/grid-item&gt;
&lt;/div&gt;

The GridList view contains the columns and the rows of the grid:

&lt;div class=&quot;row bootstrap-admin-no-edges-padding&quot; style=&quot;margin-top: 10px;&quot;&gt;
&lt;div class=&quot;col-md-12&quot;&gt;
&lt;input type=&quot;button&quot; id=&quot;btn-new&quot; value=&quot;New&quot; class=&quot;btn btn-primary&quot; ng-click=&quot;New()&quot; /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;row bootstrap-admin-no-edges-padding&quot; style=&quot;margin-top: 10px;&quot;&gt;
&lt;div class=&quot;col-md-12&quot;&gt;
&lt;div class=&quot;panel panel-default&quot;&gt;
&lt;div class=&quot;panel-heading&quot;&gt;
&lt;div class=&quot;text-muted bootstrap-admin-box-title&quot;&gt;
{{list.Title}}
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;panel-body&quot;&gt;
&lt;table class=&quot;table table-hover&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th ng-repeat=&quot;column in list.Columns&quot; grid-column=&quot;column&quot;&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr ng-repeat=&quot;row in list.Rows | orderBy: 'Entity.' + order&quot; grid-row=&quot;row&quot;&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

GridColumn, GridRow and GridCell compose the table structure; the GridItem view is the detail of the row:

&lt;div class=&quot;row bootstrap-admin-no-edges-padding&quot; style=&quot;margin-top: 10px;&quot;&gt;
&lt;div class=&quot;col-md-12&quot;&gt;
&lt;input type=&quot;button&quot; id=&quot;btn-save&quot; value=&quot;Save&quot; class=&quot;btn btn-primary&quot; ng-click=&quot;Save()&quot; ng-disabled=&quot;form.$invalid&quot; /&gt;
&lt;input type=&quot;button&quot; id=&quot;btn-delete&quot; value=&quot;Delete&quot; class=&quot;btn btn-danger&quot; ng-click=&quot;Delete()&quot; ng-show=&quot;!isNew&quot; /&gt;
&lt;input type=&quot;button&quot; id=&quot;btn-close&quot; value=&quot;Close&quot; class=&quot;btn btn-default&quot; ng-click=&quot;Close()&quot; /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;row bootstrap-admin-no-edges-padding&quot; style=&quot;margin-top: 10px;&quot;&gt;
&lt;div class=&quot;col-md-12&quot;&gt;
&lt;form name=&quot;form&quot; role=&quot;form&quot;&gt;
&lt;div class=&quot;panel panel-default&quot;&gt;
&lt;div class=&quot;panel-heading&quot;&gt;
&lt;div class=&quot;text-muted bootstrap-admin-box-title&quot;&gt;
{{item.Name}}
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;panel-body&quot;&gt;
&lt;div class=&quot;form-group&quot; ng-repeat=&quot;property in item.Properties&quot;&gt;
&lt;label for=&quot;{{property.Name}}&quot;&gt;{{property.Name}}:&lt;/label&gt;
&lt;input field tipology=&quot;GetMetadataProperty(property.Name)&quot; id=&quot;{{property.Name}}&quot; class=&quot;form-control&quot; ng-model=&quot;property.Value&quot; ng-required=&quot;!property.Nullable&quot; /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;col-md-2&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/form&gt;
&lt;/div&gt;
&lt;/div&gt;

Application

To use the directive you need to load the model, the rows and define the CRUD methods:

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

export class CitiesController {
grid: models.Grid;
rowModel: models.Row;
toaster: ngtoaster.IToasterService;

private citiesService: services.CitiesService;
private constant: commons.Constants;

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

this.grid = new models.Grid();
this.grid.Title = 'Cities';
this.Load();
}

private Load() {
var columns: Array&lt;string&gt; = new Array&lt;string&gt;('Name');
var vm = this;

this.citiesService.getMetadata(columns).then((data) =&gt; {
vm.grid.Columns = data;
vm.rowModel = this.citiesService.createGridData(data);

this.citiesService.getGridData(data).then((data) =&gt; {
vm.grid.Rows = data;
vm.toaster.success('Cities loaded successfully.');
return;
}, (error) =&gt; {
vm.toaster.error('Error loading cities', error.message);
});

}, (error) =&gt; {
vm.toaster.error('Error loading cities metadata', error.data.message);
});
}

public Save(item: models.Row) {
var vm = this;
var isNew: boolean = false;

if (item.Entity.Id == commons.Constants.GuidEmpty)
isNew = true;

this.citiesService.saveGridData(item).then((data: models.Row) =&gt; {
if (isNew)
vm.grid.Rows.push(data);

this.toaster.success(&quot;City saved successfully.&quot;);
}, (error: any) =&gt; {
this.toaster.error(&quot;Error saving city&quot;, error.data.message);
});
}

public Delete(item: models.Row) {
var vm = this;
this.citiesService.deleteGridData(item).then((data: any) =&gt; {
var index = vm.grid.Rows.indexOf(item);
vm.grid.Rows.splice(index, 1);

this.toaster.success(&quot;City deleted successfully.&quot;);
}, (error: any) =&gt; {
this.toaster.error(&quot;Error deleting city&quot;, error.data.message);
});
}
}

The final step is the view:

&lt;grid list=&quot;vm.grid&quot; row-model=&quot;vm.rowModel&quot; order=&quot;Name&quot; save=&quot;vm.Save(item)&quot; delete=&quot;vm.Delete(item)&quot;&gt;&lt;/grid&gt;

 

You can find the source code here.

 

 

Manage tables data with AngularJS Part 2: nested directives

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

Angular implicit annotation with Gulp

As you know, AngularJS provide the management of the module dependencies through the dependency injection.

In order to be able to manage these dependencies, Angular allows specific annotations that the developers can use to define their modules.

Dependency Annotation

For example, in an Angular application written with Typescript, the dependencies can be inject in the run method with this syntax:


AngularSignalR.module.config(['$stateProvider', '$urlRouterProvider', ($stateProvider, $urlRouterProvider) =&gt; {
$urlRouterProvider.otherwise(&quot;/orders&quot;);

$stateProvider
.state('Orders', {
url: '/orders',
templateUrl: '/app/views/orders.html',
controller: 'OrdersController as vm'
});
}]);

This is the most used syntax and is called Inline Array Annotation; another option is using the $inject property:


module AngularSignalRApp.Controllers {

export class ModalsController {
private modalInstance: ng.ui.bootstrap.IModalServiceInstance;

public static $inject = ['$uibModalInstance'];

constructor(modalInstance: ng.ui.bootstrap.IModalServiceInstance) {
this.modalInstance = modalInstance;
}

}

AngularSignalR.module.controller('ModalsController', ModalsController);
}

The order specified in the $inject property must match the controller arguments; this is the Property Annotation.

The last options allow to leave out the manual injection of the parameters and assuming that the parameter names are the names of the dependencies; this is called implicit annotation:


module AngularSignalRApp.Controllers {

export class ModalsController {
private modalInstance: ng.ui.bootstrap.IModalServiceInstance;

constructor($uibModalInstance: ng.ui.bootstrap.IModalServiceInstance) {
this.modalInstance = $uibModalInstance;
}
}

AngularSignalR.module.controller('ModalsController', ModalsController);
}

Gulp plugin

Unfortunately, the implicit annotation doesn’t work with the minification process due to the rename of the parameters.

You can work around this problem by using the Gulp plugin gulp-ng-annotate; first of all, you need to install it as a project dev dependency:

npm install gulp-ng-annotate –save-dev

And add it in the gulpfile.js of the web application:


var gulp = require('gulp'),
ngAnnotate = require('gulp-ng-annotate')

Now you need to add the plugin inside the task that execute the minification process.

In this application it’s used gulp-useref, and you need to add the plugin before the uglify process:


gulp.task('useref', ['inject'], function () {
var source = gulp.src(config.indexDev);
var jsFilter = filter('**/*.js', { restore: true });
var cssFilter = filter('**/*.css', { restore: true });

return source
.pipe(useref())
.pipe(cssFilter)
.pipe(cleanCss())
.pipe(cssFilter.restore)
.pipe(jsFilter)
.pipe(ngAnnotate())
.pipe(uglify())
.pipe(jsFilter.restore)
.pipe(rev())
.pipe(revReplace())
.pipe(gulp.dest(config.dist));
});

With this operations, we can using implicit annotation without encountering problems after minification process.

You can find the project here.

 

 

Angular implicit annotation with Gulp

ASP.NET SignalR and TypeScript in an AngularJS SPA

SignalR is a javascript library for helping developers to add real-time functionalities to web applications.

This example is a Angular SPA developed using Typescript,  to take advantage of Object Oriented features in the client-side code development.

This application has a simple orders list, that every user can manage; every time a user make a change to an order or create a new one, every user window will be automatically refreshed with the new changes.

The application using SQL database, Entity Framework Code First, ASP.NET Web API, Angular 1.5, TypeScript and Bootstrap as front-end framework.

The first steps are server side implementations:

  • Entity Framework entity
  • Web API for CRUD operations
  • SignalR Hub

The EF Order table entity:

[Table("Orders")]
public class Order
{
public Guid Id { get; set; }
public string Article { get; set; }
public decimal Amount { get; set; }
public string Customer { get; set; }
public DateTime CreationDate { get; set; }
public string Notes { get; set; }
}

After creating the context class and performed the migrations commands, you can proceed with the implementation of the Web API Controller; ASP.NET scaffolding is very useful for a standard implementation:

public class OrdersController : ApiController
{
private Context db = new Context();

// GET: api/Orders
public IQueryable<Order> GetOrders()
{
return db.Orders.OrderByDescending(o => o.CreationDate);
}

// GET: api/Orders/5
[ResponseType(typeof(Order))]
public async Task<IHttpActionResult> GetOrder(Guid id)
{
Order order = await db.Orders.FindAsync(id);
if (order == null)
{
return NotFound();
}

return Ok(order);
}

// PUT: api/Orders/5
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutOrder(Guid id, Order order)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

if (id != order.Id)
{
return BadRequest();
}

db.Entry(order).State = EntityState.Modified;

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

return StatusCode(HttpStatusCode.NoContent);
}

// POST: api/Orders
[ResponseType(typeof(Order))]
public async Task<IHttpActionResult> PostOrder(Order order)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

order.Id = Guid.NewGuid();
db.Orders.Add(order);

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

return CreatedAtRoute("DefaultApi", new { id = order.Id }, order);
}

// DELETE: api/Orders/5
[ResponseType(typeof(Order))]
public async Task<IHttpActionResult> DeleteOrder(Guid id)
{
Order order = await db.Orders.FindAsync(id);
if (order == null)
{
return NotFound();
}

db.Orders.Remove(order);
await db.SaveChangesAsync();

return Ok(order);
}

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

private bool OrderExists(Guid id)
{
return db.Orders.Count(e => e.Id == id) > 0;
}
}

Then, the implementation relating the SignalR Hub; you have to add the Startup.cs file if not present and register the SignalR middleware:

using Owin;
using Microsoft.Owin;

[assembly: OwinStartup(typeof(AngularSignalR.Startup))]
namespace AngularSignalR
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}

Then, the SignalR Hub:

public class NotificationsHub : Hub
{
public async Task OrderChanges()
{
await Clients.Others.NotifyOrderChanges();
}
}

The OrderChanges method is called from every client making a change to an order; NotifyOrderChanges is the event fired to any other client connected.

Now it’s the time to starting with the AngularJS application; the key points are:

  • The factory that interact with Web API service
  • The factory that manage SignalR notifications
  • The main controller

One useful service in AngularJS framework is $resource, a factory that allows you to interact with RESTful service easily.

The dependency of the ngResource module should be declarated in the main app module, and then is possibile to use $resource service; this is the implementation of the OrdersService factory:

module AngularSignalRApp.Services {

import ngr = ng.resource;

export interface IOrdersResourceClass extends ngr.IResourceClass<ngr.IResource<AngularSignalRApp.Models.IOrder>> {
create(order: AngularSignalRApp.Models.IOrder);
}

export class OrdersService {
private resource: IOrdersResourceClass;

constructor($resource: ngr.IResourceService) {
this.resource = <IOrdersResourceClass> $resource('/api/orders/:id', { id: '@Id' }, {
get: { method: "GET" },
create: { method: "POST" },
save: { method: "PUT" },
query: { method: "GET", isArray: true },
delete: { method: "DELETE" }
});
}

public create(order: AngularSignalRApp.Models.IOrder) {
return this.resource.create(order);
}

public save(order: AngularSignalRApp.Models.IOrder) {
if (order.Id == GuidEmpty) {
return this.resource.create(order);
}
else {
return this.resource.save(order);
}
}

public delete(order: AngularSignalRApp.Models.IOrder) {
return this.resource.remove(order);
}

public getAll() {
return this.resource.query();
}

static factory() {
return (r) => new OrdersService(r);
}
}

AngularSignalR.module.factory('OrdersService', ['$resource', OrdersService.factory()]);
}

Another factory is NotificationService, that expose the methods to interact with SignalR Hub:

module AngularSignalRApp.Services {

export class NotificationsService {

private connection: HubConnection;
private proxy: HubProxy;
private callback;

constructor() {
this.connection = $.hubConnection();
this.proxy = this.connection.createHubProxy('NotificationsHub');

this.proxy.on('NotifyOrderChanges', () => {
this.callback();
});

this.connection.start();
}

public NotifyOrderChanges() {
this.proxy.invoke('OrderChanges');
}

public OnOrderChanges(callback) {
if (callback) {
this.callback = callback;
}
}

static factory() {
return () => new NotificationsService();
}
}

AngularSignalR.module.factory('NotificationsService', [NotificationsService.factory()]);
}

The final step is the main controller, that uses OrdersService to manage CRUD operations on the orders and NotificationsService to tell with the SignalR Hub:

module AngularSignalRApp.Controllers {

import ngr = ng.resource;

export class OrdersController {
orders: ngr.IResourceArray<ngr.IResource<AngularSignalRApp.Models.IOrder>>;
order: AngularSignalRApp.Models.IOrder;
title: string;
toaster: ngtoaster.IToasterService;

private filter: ng.IFilterService;
private modalService: ng.ui.bootstrap.IModalService;
private ordersService: AngularSignalRApp.Services.OrdersService;
private notificationsService: AngularSignalRApp.Services.NotificationsService;

public static $inject = ['$filter', '$uibModal', 'toaster', 'OrdersService', 'NotificationsService'];

constructor(filter: ng.IFilterService, modalService: ng.ui.bootstrap.IModalService, toaster: ngtoaster.IToasterService,
ordersService: AngularSignalRApp.Services.OrdersService, notificationsService: AngularSignalRApp.Services.NotificationsService) {
this.filter = filter;
this.modalService = modalService;
this.ordersService = ordersService;
this.notificationsService = notificationsService;

this.notificationsService.OnOrderChanges(() => {
this.Load();
});

this.toaster = toaster;
this.title = "Orders";

this.Load();
}

public New() {
this.order = {
Id: GuidEmpty,
Article: "",
Amount: 0,
CreationDate: new Date(),
Customer: "",
Notes: ""
};
}

public Edit(order) {
this.order = order;
}

public Delete() {

var vm = this;
var modalInstance = vm.modalService.open({
animation: true,
templateUrl: "/App/Views/Shared/ConfirmDeleteModal.html",
controller: "ModalsController as vm",
size: "modal-sm"
});

modalInstance.result.then(function () {
vm.ordersService.delete(vm.order).$promise.then((data) => {
var orderToDelete = vm.filter('filter')(vm.orders, { Id: vm.order.Id })[0];
var index = vm.orders.indexOf(orderToDelete);
vm.orders.splice(index, 1);
vm.order = null;

vm.notificationsService.NotifyOrderChanges();
vm.toaster.success("Order deleted successfully.");
}, (error) => {
vm.toaster.error("Error deleting order.", error.data.message);
});
});
}

public Save() {
this.ordersService.save(this.order).$promise.then((data) => {
if (this.order.Id == GuidEmpty) {
this.order = data;
this.orders.push(data);
}

this.notificationsService.NotifyOrderChanges();
this.toaster.success("Order saved successfully.");

}, (error) => {
this.toaster.error("Error saving order", error.data.message);
});
}

public Close() {
this.order = null;
}

private Load() {
this.ordersService.getAll().$promise.then((data) => {
this.orders = data;
this.toaster.success("Orders loaded successfully.");
return;
}, (error) => {
this.toaster.error("Error loading orders", error.data.message);
});
}
}

AngularSignalR.module.controller('OrdersController', OrdersController);
}

The comunication with the SignalR Hub occur in the New, Save and Delete methods,
and when the NotifyOrderChanges event is fired.

You can find the project here.

 

 

ASP.NET SignalR and TypeScript in an AngularJS SPA