问题
Some time ago, I setup an abstract data factory in AngularJS, and need a hint on how to simplify its use. I was initially under the impression that I would create another factory per entity that would be called from an AngularJS controller. Turns out it just looks too complicated and I would like to remove the intermediary entity factory (ContentTypesFactory
), and simply call the abstract factory directly from the AngularJS controller.
In the following example I'm wiring up a KendoUI datasource.
AbstractRepository.js:
app.factory('abstractRepository', [function () {
// we will inject the $http service as repository service
// however we can later refactor this to use another service
function abstractRepository(repositoryService, whichEntity, odataUrlBase) {
//this.http = $http;
this.http = repositoryService;
this.whichEntity = whichEntity;
this.odataUrlBase = odataUrlBase;
this.route;
}
abstractRepository.prototype = {
getList: function () {
return this.http.get(this.odataUrlBase);
},
get: function (id) {
return this.http.get(this.odataUrlBase + '/' + id);
},
insert: function (entity) {
return this.http.post(this.odataUrlBase, entity);
},
update: function (entity) {
return this.http.put(this.odataUrlBase + '/' + entity.ID, this.whichEntity);
},
remove: function (id) {
return this.http.delete(this.odataUrlBase + '/' + id);
}
};
abstractRepository.extend = function (repository) {
repository.prototype = Object.create(abstractRepository.prototype);
repository.prototype.constructor = repository;
}
return abstractRepository;
}]);
ContentTypesFactory.js:
/// <reference path="../Scripts/angular.min.js" />
/// <reference path="../app.js" />
/// <reference path="AbstractRepository.js" />
// each function returns a promise that can be wired up to callback functions by the caller
// the object returned from the factory is a singleton and can be reused by different controllers
app.factory('contentTypesRepository', ['$http', 'abstractRepository', function ($http, abstractRepository) {
var odataUrlBase = '/odata/ContentTypes'
var whichEntity = 'ContentTypes';
function contentTypesRepository() {
abstractRepository.call(this, $http, whichEntity, odataUrlBase);
}
abstractRepository.extend(contentTypesRepository);
return new contentTypesRepository();
}]);
ContentTypesController.js:
app.controller('contentTypesController', ['$scope', '$log', 'contentTypesRepository',
function ($scope, $log, contentTypesFactory) {
$scope.odataUrlBase = '/odata/ContentTypes';
$scope.status;
//
// Grid
//
$scope.contentTypesDataSource = new kendo.data.HierarchicalDataSource({
type: "odata",
transport: {
create: {
url: $scope.odataUrlBase
},
read: {
type: "GET",
url: $scope.odataUrlBase,
//function (data) {
// pass in the URL to the abstract factory
//},
dataType: "json" // the default result type is JSONP, but WebAPI does not support JSONP
},
update: {
contentType: "application/json",
url: function (data) {
return $scope.odataUrlBase + '(' + data.ContentTypesId + ')';
} . . .
Now, removing the reference to ContentTypesFactory, my new controller code looks like this:
app.controller('contentTypesController', ['http', '$scope', '$log', 'abstractDataFactory',
// the abstract data factory accepts controller type parameters for RESTful CRUD
function ($scope, $log, abstractDataFactory) {
//function ContentTypeController($scope) {
var crudServiceBaseUrl = "/odata/ContentTypes";
var odataUrlBase = '/odata/ContentTypes'
var whichEntity = 'ContentTypes';
// Pulled the following code from the ControlerTypesFactory, but not sure how to implement it in this ContentTypesController:
//function contentTypesRepository() {
// abstractRepository.call(this, $http, whichEntity, odataUrlBase);
//}
//abstractRepository.extend(contentTypesRepository);
//return new contentTypesRepository();
$scope.greeting = 'Hola!';
var dataSource = new kendo.data.DataSource({
type: "odata",
transport: {
read: {
url: crudServiceBaseUrl,
dataType: "json"
},
update: { // PUT
url: function (data) {
console.log(data);
dataType: "json"
return crudServiceBaseUrl + "(" + data.ContentTypeId + ")";
},
error: function (e) {
console.log("error: " + e);
}
}, . . .
I'm sure the change is not that difficult, but I'm fairly new to Angular. How can I wire this up so to use the abstract repository, and looking for any other Angular best-practices and naming suggestions?
回答1:
Taking a fresh look at this this morning, I believe I have a working solution.
Once again, I wanted to remove the intermediary factory abstraction, and have only one abstract data fetching factory that I could pass in parameters directly from from each controller.
$http
is injected into the factory which takes in two parameters whichEntity
, odataUrlBase
, and are set upon instantiation. The factory exposes methods like getList(odataOptions)
. It is these options that are required to to be passed along with the requests as part of the query string, and are done so through $http
's params
.
The new abstract factory is injected into the controller, then instantiated with the required parameters:
app.controller('contentTypeController', ['$scope', '$log', 'abstractFactory3',
function ($scope, $log, abstractFactory3) {
var dataFactory = new abstractFactory3("ContentType", "/odata/ContentType");
Here is the new abstract factory:
app.factory('abstractFactory3', function ($http) {
function abstractFactory3(whichEntity, odataUrlBase) {
this.whichEntity = whichEntity;
this.odataUrlBase = odataUrlBase;
}
abstractFactory3.prototype = {
getList: function (odataOptions) {
//var result = $http({
// url: this.odataUrlBase,
// method: 'GET',
// params: odataParams
//});
return $http.get(this.odataUrlBase, {
params: odataOptions
});
}
};
return abstractFactory3;
});
Since this is being used through through a kendo.data.DataSource
, we have to extract the OData parameters:
var odataParams = kendo.data.transports["odata"].parameterMap(options.data, "read");
Upon a successful get
, we pass back the data source's options
with the result:
var dataSource = new kendo.data.DataSource({
type: "odata",
transport: {
read:
function (options) {
var odataParams = kendo.data.transports["odata"].parameterMap(options.data, "read");
dataFactory.getList(odataParams)
.success(function (result) {
options.success(result);
});
//$http({ // // example of using $http directly ($http injection was removed from this controller, as this is now handled by the abstract data factory)
// url: crudServiceBaseUrl,
// method: 'GET',
// params: odataParams
//})
//.success(function (result) {
// options.success(result);
//});
}, // update, create, destroy, . . .
I realize that in my original question, the controller was in fact using the Kendo transport, rather than the Angular factory that I had setup.
So what I have now is:
angular controller (KendoUI datasource) -> angular data factory
I have also reworded the question in order to more-accurately reflect what the entire problem was, and hope this is of assistance to someone.
There are some other little nuances, like getting and passing data ID's, objects, JSON.stringification that are best shown in a full solution. Hopefully the following helps:
AngularJS abstract data factory:
app.factory('abstractFactory3', function ($http) {
function abstractFactory3(odataUrlBase) {
this.odataUrlBase = odataUrlBase;
}
abstractFactory3.prototype = {
getList: function (odataOptions) {
//var result = $http({
// url: this.odataUrlBase,
// method: 'GET',
// params: odataParams
//});
return $http.get(this.odataUrlBase, {
params: odataOptions
});
},
get: function (id, odataOptions) {
return $http.get(this.odataUrlBase + '/' + id, {
params: odataOptions
});
},
insert: function (data) {
return $http.post(this.odataUrlBase, data);
},
update: function (id, data) {
return $http.put(this.odataUrlBase + '(' + id + ')', data);
},
remove: function (id) {
return $http.delete(this.odataUrlBase + '(' + id + ')');
}
};
return abstractFactory3;
});
AngularJS Controller:
app.controller('contentTypeController', ['$scope', '$log', 'abstractFactory3',
// the abstract data factory accepts controller type parameters for RESTful CRUD
function ($scope, $log, abstractFactory3) {
var dataFactory = new abstractFactory3("/odata/ContentType");
var crudServiceBaseUrl = "/odata/ContentType";
var dataSource = new kendo.data.DataSource({
type: "odata",
transport: {
read:
function (options) {
var odataParams = kendo.data.transports["odata"].parameterMap(options.data, "read");
dataFactory.getList(odataParams)
.success(function (result) {
options.success(result);
})
.error (function (error) {
console.log("data error");
});
},
update:
function (options) {
var data = options.data;
dataFactory.update(data.ContentTypeId, data)
.success(function (result) {
options.success(result);
})
.error(function (error) {
console.log("data error");
});
},
create:
function (options) {
var data = options.data;
data.ContentTypeId = "0"; // required for valid field data
dataFactory.insert(data)
.success(function (result) {
options.success(result);
})
.error(function (error) {
console.log("data error");
});
},
destroy:
function (options) {
var data = options.data;
dataFactory.remove(data.ContentTypeId)
.success(function (result) {
options.success(result);
})
.error(function (error) {
console.log("data error");
});
},
parameterMap: function (options, type) {
// this is optional - if we need to remove any parameters (due to partial OData support in WebAPI
if (operation !== "read" && options.models) {
return JSON.stringify({ models: options });
}
},
},
batch: false,
pageSize: 10,
serverPaging: true,
change: function (e) {
console.log("change: " + e.action);
// do something with e
},
schema: {
data: function (data) {
//console.log(data)
return data.value;
},
total: function (data) {
console.log("count: " + data["odata.count"]);
return data["odata.count"];
},
model: {
id: "ContentTypeId",
fields: {
ContentTypeId: { editable: false, nullable: true },
//UserId: {editable: false, nullable: false },
Description: { type: "string", validation: { required: true } },
//msrepl_tran_version: { type: "string", validation: { required: true } }
}
}
},
error: function (e) {
//var response = JSON.parse(e.responseText);
var response = e.status;
console.log(response);
}
});
$("#grid").kendoGrid({
dataSource: dataSource,
pageable: true,
height: 400,
toolbar: ["create"],
columns: [
{ field: "ContentTypeId", editable: false, width: 90, title: "ID" },
{ field: "Description", title: "Content Type" },
{ command: ["edit", "destroy"] }
],
editable: "inline"
});
}]);
回答2:
You mention best practices, I think it's better to leave your data source and manipulation in a factory or service like the data factories you had. Here's some specific references:
- http://trochette.github.io/Angular-Design-Patterns-Best-Practices/#/keep_controllers_simple
- http://trochette.github.io/Angular-Design-Patterns-Best-Practices/#/business_logic
来源:https://stackoverflow.com/questions/21679701/how-to-send-kendoui-data-source-odata-parameters-to-an-abstract-restful-angularj