数据管理
Angular Service提供了一个非常好的方式来组织models和数据管理层代码的方式。
Models提供状态和业务逻辑。
最好的实践方案是数据以面向对象的方式创建一个类,该类既维护一个对象的状态又提供处理类状态的
业务逻辑。
通过使用一个函数闭包来定义model,使用prototypal继承来定义跟model相关的业务逻辑代码。
最后使用Angular模块的value方法让model注入到我们的应用程序里。
比如下面的代码定义了Fermentable model,所有的model属性定义在一个闭包中,两个方法则定义在
model的原型里。
var Fermentable = function(){
var self = this;
self.name = '';
self.type = ''; // can be "Grain", "Sugar", "Extract", "Dry
// Extract" or "Adjunct"
self.amount = 0.0;
self.yield = 0.0; // percent
self.color = 0.0; // Lovibond units (SRM)
self.addAfterBoil = false;
self.origin = '';
self.supplier = '';
self.notes = '';
self.coarseFineDiff = 0.0; // percent
self.moisture = 0.0; // percent
self.diastaticPower = 0.0; // in lintner units
self.protein = 0.0; // percent
self.maxInBatch = 0.0; // percent
self.recommendMash = false;
self.ibuGalPerPound = 0.0;
self.displayAmount = '';
self.potential = 0.0;
self.displayColor = 0.0;
self.extractSubstitute = '';
};
Fermentable.prototype =
{
/**
* calculates the gravity per pound for the fermentable
* @param brewHouseEfficiency - the estimated brew house
* efficiency
* @returns {number} - potential extract per pound for the
* fermentable
*/
gravityPerPound: function(brewHouseEfficiency){
return ((this.potential - 1) * 1000) * brewHouseEfficiency;
},
/**
* calculates the gravity for the ingredient
* @param brewHouseEfficiency - the estimated brew house
* efficiency
* @returns {number} - returns the total potential extract for
* the fermentable
*/
ingredientGravity: function(brewHouseEfficiency){
return this.amount * this.gravityPerPound(brewHouseEfficiency);
}
};
angular.module('brew-everywhere').value('Fermentable',Fermentable);
要在我们的Controller,directives或者services中使用它,我们可以直接将model的名字包含到我们的组件定义函数中。
angular.module('brew-anywhere').factory('fermentableDataService',
function(messaging, events, mongolab, modelTransformer,Fermentable) {
});
如果需要创建该model的新实例,则可以直接new
$scope.fermentable = new Fermentable();
K.Scott Allen提供了一个通用转换服务,将从外部数据服务接收到的普通JSON对象转换成我们的model对象。
modelTransformer ,以一个JSON对象和一个model值,然后用AngularJS的扩展方法将model应用到JSON对象。
angular.module('brew-everywhere').factory('modelTransformer',function(){
var transformObject = function(jsonResult,constructor){
var model = new constructor();
angular.extend(model,jsonResult);
return model;
};
var transformResult = function(jsonResult,constructor){
if(angular.isArray(jsonResult)){
var models = [];
angular.forEach(jsonResult,function(object){
models.push(transformObject(object,constructor));
});
return models;
}else {
return transformObject(jsonResult,constructor);
}
};
var modelTransformer = {
transform: transformResult
};
return modelTransformer;
});
该服务用法:
var model = modelTransformer.transform(fermentable,Fermentable);
因为我们已经将models定义成了values和一个transformer 服务,我们就可以将注意力转移到各类数据服务上了。
实现CRUD 数据服务:
angular.module('brew-everywhere')
.factory('mongolab', function ($http) {
var apiKey = '';
var baseUrl = 'https://api.mongolab.com/api/1/databases';
var setApiKey = function (apikey) {
apiKey = apikey;
};
var getApiKey = function () {
return apiKey;
};
var setBaseUrl = function (uri) {
baseUrl = uri;
};
var getBaseUrl = function () {
return baseUrl;
};
var query = function (database, collection, parameters) {
parameters = parameters || {};
parameters['apiKey'] = apiKey;
var uri = baseUrl + '/' + database + '/collections/' + collection;
return $http({method: "GET", url: uri, params: parameters,cache: false});
};
var queryById = function (database, collection, id, parameters)
{
parameters = parameters || {};
parameters['apiKey'] = apiKey;
var uri = baseUrl + '/' + database + '/collections/' + collection + '/' + id;
return $http({method: "GET", url: uri, params: parameters,cache: false});
};
var createObject = function (database, collection, object) {
var uri = baseUrl + '/' + database + '/collections/' +
collection + '?apiKey=' + apiKey;
return $http({method: "POST", url: uri, data:JSON.stringify(object), cache: false});
};
var updateObject = function (database, collection, object) {
var uri = baseUrl + '/' + database + '/collections/' +
collection + '/' + object._id.$oid + '?apiKey=' + apiKey;
return $http({method: "PUT", url: uri, data:JSON.stringify(object), cache: false});
};
var deleteObject = function (database, collection, object) {
var uri = baseUrl + '/' + database + '/collections/' +
collection + '/' + object._id.$oid + '?apiKey=' + apiKey;
return $http({method: "DELETE", url: uri, cache: false});
};
var mongolab = {
setApiKey: setApiKey,
getApiKey: getApiKey,
setBaseUrl: setBaseUrl,
getBaseUrl: getBaseUrl,
query: query,
queryById: queryById,
create: createObject,
update: updateObject,
delete: deleteObject
};
return mongolab;
});
每个服务都是由get{type},get{type}ById,create{type},update{type},和delete{type}组成。
同时该服务使用messaging服务来反应事件执行相关服务调用并在服务完成后回复消息。
在定义具体数据服务类时,每个核心方法都会遵循类似的模式:定义一个方法来处理事件执行,
创建两个$http回复处理器方法,一个负责处理成功回复一个负责处理失败消息,最后,调用messaging服务
来预订执行事件。
每个成功处理方法都使用modelTransformer服务来转换返回的原生JSON对象到特定的model对象,并
发布一个完成消息。
每个错误处理方法都发布一个请求失败消息和一个给error服务的消息来添加一个消息使其通知用户请求失败。
该消息可以被进一步扩展,将错误消息作为一个方法参数并发送该错误消息到error服务:
angular.module('brew-everywhere')
.factory('adjunctDataService', function (messaging, events,
mongolab, modelTransformer, Adjunct) {
var adjuncts = [];
var getAdjuncts = function () {
mongolab.query('breweverywhere_backup', 'adjuncts', [])
.then(getAdjunctSuccessHandler, getAdjunctErrorHandler);
};
var getAdjunctSuccessHandler = function (response) {
if (response.length > 0) {
var result = [];
angular.forEach(response, function (adjunct) {
result.push(modelTransformer.transform(adjunct, Adjunct));
});
messaging.publish(events.message._GET_ADJUNCTS_COMPLETE_,[result]);
} else {
getAdjunctErrorHandler();
}
};
var getAdjunctErrorHandler = function(){
messaging.publish(events.message._GET_ADJUNCTS_FAILED_);
messaging.publish(events.message._ADD_ERROR_MESSAGE_,['Unable to get adjuncts from server', 'alert.error']);
};
messaging.subscribe(events.message._GET_ADJUNCTS_, getAdjuncts);
…
…
var init = function(){
adjuncts = [];
};
var adjunctDataService = {
init: init,
getAdjuncts: getAdjuncts,
getAdjunctById: getAdjunctById,
createAdjunct: createAdjunct,
updateAdjunct: updateAdjunct,
deleteAdjunct: deleteAdjunct
};
return adjunctDataService;
});
缓存数据减少网络拥堵:
Angular提供了一个服务可以让我们将数据缓存在客户端,它就是 $cacheFactory服务,它可以让我们
在客户端创建一个命名缓存,并可以使用命名值对来保存数据。
angular.module('brew-everywhere')
.factory('adjunctDataService', function ($cacheFactory,
messaging, events, mongolab, modelTransformer, Adjunct) {
var cache = $cacheFactory('brew-everywhere');
var getAdjuncts = function () {
var adjunctList = cache.get('adjuncts');
if(adjunctList && adjunctList.length > 0){
messaging.publish(events.message._GET_ADJUNCTS_COMPLETE_,[adjunctList]);
return;
}
mongolab.query('breweverywhere_backup', 'adjuncts', [])
.then(getAdjunctSuccesHandler, getAdjunctErrorHandler);
};
var getAdjunctSuccesHandler = function (response) {
if (response.length > 0) {
var result = [];
angular.forEach(response, function (adjunct) {
result.push(modelTransformer.transform(adjunct, Adjunct));
});
cache.put('adjuncts', result);
messaging.publish(events.message._GET_ADJUNCTS_COMPLETE_,[result]);
} else {
getAdjunctErrorHandler();
}
};
要跨整个AngularJS 应用缓存数据,我们需要考虑使用浏览器本地存储机制。
有一个开源的angular-local-storage组件提供了方便访问浏览器本地存储的服务。
https://github.com/grevory/angular-local-storage
angular.module('brew-everywhere')
.factory('adjunctDataService', function (localStorageService,
messaging, events, mongolab, modelTransformer, Adjunct) {
var getAdjuncts = function () {
var adjunctList = localStorageService.get('adjuncts');
if(adjunctList && adjunctList.length > 0){
messaging.publish(events.message._GET_ADJUNCTS_COMPLETE_,[adjunctList]);
return;
}
mongolab.query('breweverywhere_backup', 'adjuncts', [])
.then(getAdjunctSuccessHandler, getAdjunctErrorHandler);
};
var getAdjunctSuccessHandler = function (response) {
if (response.length > 0) {
var result = [];
angular.forEach(response, function (adjunct) {
result.push(modelTransformer.transform(adjunct, Adjunct));
});
localStorageService.set('adjuncts', result);
messaging.publish(events.message._GET_ADJUNCTS_COMPLETE_,[result]);
} else {
getAdjunctErrorHandler();
}
};
另外BreezeJS类库是一个更加强健的数据缓存处理方案。http://breezejs.com.
服务中的数据转换:
有时候我们需要为一个指令或者controller返回一个数据子集或者需要将数据转换为其它格式。
我们可以借助AngularJS的过滤器或者使用外部的一些类库比如 underscore 或者 loadash
Filter是设计用来作为ngRepeat指令的一个选项来限制显示数量。
angular.module('brew-everywhere').filter('fermentableType', function(){
return function(input,arg){
var result = [];
angular.forEach(input,function(item){
if(item.type === arg){
result.push(item);
}
});
return result;
};
});
使用它提供过滤:
<table class="table table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Potential</th>
<th>SRM</th>
<th>Amount</th>
<th> </th>
</tr>
</thead>
<tbody>
<tr ng-repeat="fermentable in fermentables | fermentableType:'Grain'">
<td class="col-xs-4">{{fermentable.name}}</td>
<td class="col-xs-2">{{fermentable.type}}</td>
<td class="col-xs-2">{{fermentable.potential}}</td>
<td class="col-xs-2">{{fermentable.color}}</td>
</tr>
</tbody>
</table>
更复杂一点的Filter定义:
angular.module('brew-everywhere').filter('filterFermentable', function () {
return function (input, arg) {
var result = [];
angular.forEach(input, function (item) {
var add = true
for (var key in arg) {
if (item.hasOwnProperty(key)) {
if (item[key] !== arg[key]) {
add = false;
}
}
}
if (add) {
result.push(item);
}
});
return result;
};
});
<table class="table table-bordered">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Potential</th>
<th>SRM</th>
<th>Amount</th>
<th> </th>
</tr>
</thead>
<tbody>
<tr ng-repeat="fermentable in fermentables | filterFermentable:{type:'Grain', color: 3}">
<td class="col-xs-4">{{fermentable.Name}}</td>
<td class="col-xs-2">{{fermentable.Type}}</td>
<td class="col-xs-2">{{fermentable.Potential}}</td>
<td class="col-xs-2">{{fermentable.Color}}</td>
</tr>
</tbody>
</table>
我们还可以直接将filter应用于Service
angular.module('brew-everywhere').factory('fermentableDataService',
function(messaging, events, mongolab, modelTransformer,Fermentable, fermentableType) {
var fermentables = [];
var filterByType = function(type){
return fermentableType(fermentables, type);
}
}
还有一种filter是用于数组转换,过滤的:
angular.module('brew-everywhere').filter('translateFermentableToDisplay', function () {
return function (input) {
var result = [];
angular.forEach(input, function (item) {
result.push({name: item.name, type: item.type});
});
return result;
};
});
我们在服务中使用上面定义的数组转换过滤器:
angular.module('brew-everywhere').factory('fermentableDataService',
function(messaging, events, mongolab, modelTransformer,Fermentable, translateFermentableToDisplay) {
var fermentables = [];
var forDisplay = function(){
return translateFermentableToDisplay(fermentables);
}
}
使用AngularJS filter来缩减和转换应用程序数据格式是另一个我们可以用来封装数据相关业务逻辑的好方法。
如果filter无法满足时可以考虑使用外部类库,比如underscore或者lodash。
它们提供了超过80个工具来用于排序,查找和缩减数组对象,函数型编程模式。