AngularJS 在其文档中明确指出服务是单例:
AngularJS services are singletons
与直觉相反,module.factory
它还返回一个 Singleton 实例。
鉴于非单例服务有很多用例,实现工厂方法以返回服务实例的最佳方法是什么,以便每次ExampleService
声明依赖项时,它都由不同的实例来满足ExampleService
?
我不完全确定您要满足什么用例。但是可以让工厂返回对象的实例。您应该能够修改它以满足您的需要。
var ExampleApplication = angular.module('ExampleApplication', []);
ExampleApplication.factory('InstancedService', function(){
function Instance(name, type){
this.name = name;
this.type = type;
}
return {
Instance: Instance
}
});
ExampleApplication.controller('InstanceController', function($scope, InstancedService){
var instanceA = new InstancedService.Instance('A','string'),
instanceB = new InstancedService.Instance('B','object');
console.log(angular.equals(instanceA, instanceB));
});
更新
考虑以下对非单例服务的请求。布莱恩福特在其中指出:
所有服务都是单例的想法并没有阻止您编写可以实例化新对象的单例工厂。
以及他从工厂返回实例的示例:
myApp.factory('myService', function () {
var MyThing = function () {};
MyThing.prototype.foo = function () {};
return {
getInstance: function () {
return new MyThing();
}
};
});
我还认为他的示例更好,因为您不必new
在控制器中使用关键字。它被封装在getInstance
服务的方法中。
我认为我们不应该让工厂返回一个有new
能力的函数,因为这开始破坏依赖注入并且库的行为会很笨拙,尤其是对于第三方而言。简而言之,我不确定非单例服务是否有任何合法用例。
完成同样事情的更好方法是使用工厂作为 API 来返回对象集合,并附加了 getter 和 setter 方法。下面是一些伪代码,展示了如何使用这种服务:
.controller( 'MainCtrl', function ( $scope, widgetService ) {
$scope.onSearchFormSubmission = function () {
widgetService.findById( $scope.searchById ).then(function ( widget ) {
// this is a returned object, complete with all the getter/setters
$scope.widget = widget;
});
};
$scope.onWidgetSave = function () {
// this method persists the widget object
$scope.widget.$save();
};
});
这只是通过 ID 查找小部件然后能够保存对记录所做的更改的伪代码。
这是该服务的一些伪代码:
.factory( 'widgetService', function ( $http ) {
function Widget( json ) {
angular.extend( this, json );
}
Widget.prototype = {
$save: function () {
// TODO: strip irrelevant fields
var scrubbedObject = //...
return $http.put( '/widgets/'+this.id, scrubbedObject );
}
};
function getWidgetById ( id ) {
return $http( '/widgets/'+id ).then(function ( json ) {
return new Widget( json );
});
}
// the public widget API
return {
// ...
findById: getWidgetById
// ...
};
});
尽管未包含在此示例中,但这些类型的灵活服务也可以轻松管理状态。
我现在没有时间,但如果有帮助,我可以稍后组装一个简单的 Plunker 来演示。
另一种方法是使用 复制服务对象angular.extend()
。
app.factory('Person', function(){
return {
greet: function() { return "Hello, I'm " + this.name; },
copy: function(name) { return angular.extend({name: name}, this); }
};
});
然后,例如,在您的控制器中
app.controller('MainCtrl', function ($scope, Person) {
michael = Person.copy('Michael');
peter = Person.copy('Peter');
michael.greet(); // Hello I'm Michael
peter.greet(); // Hello I'm Peter
});
这是一个笨拙的。
我知道这篇文章已经得到了回答,但我仍然认为在一些合法的场景中你需要非单例服务。假设有一些可重用的业务逻辑可以在多个控制器之间共享。在这种情况下,放置逻辑的最佳位置是服务,但是如果我们需要在可重用逻辑中保留一些状态怎么办?然后我们需要非单例服务,以便可以在应用程序中的不同控制器之间共享。这就是我实现这些服务的方式:
angular.module('app', [])
.factory('nonSingletonService', function(){
var instance = function (name, type){
this.name = name;
this.type = type;
return this;
}
return instance;
})
.controller('myController', ['$scope', 'nonSingletonService', function($scope, nonSingletonService){
var instanceA = new nonSingletonService('A','string');
var instanceB = new nonSingletonService('B','object');
console.log(angular.equals(instanceA, instanceB));
}]);
这是我的非单例服务示例,它来自我正在研究的 ORM。在示例中,我展示了一个基础模型 (ModelFactory),我希望 services('users','documents') 继承和潜在扩展。
在我的 ORM 中,ModelFactory 注入其他服务以提供使用模块系统沙盒化的额外功能(查询、持久性、模式映射)。
在示例中,用户和文档服务具有相同的功能,但具有各自独立的范围。
/*
A class which which we want to have multiple instances of,
it has two attrs schema, and classname
*/
var ModelFactory;
ModelFactory = function($injector) {
this.schema = {};
this.className = "";
};
Model.prototype.klass = function() {
return {
className: this.className,
schema: this.schema
};
};
Model.prototype.register = function(className, schema) {
this.className = className;
this.schema = schema;
};
angular.module('model', []).factory('ModelFactory', [
'$injector', function($injector) {
return function() {
return $injector.instantiate(ModelFactory);
};
}
]);
/*
Creating multiple instances of ModelFactory
*/
angular.module('models', []).service('userService', [
'ModelFactory', function(modelFactory) {
var instance;
instance = new modelFactory();
instance.register("User", {
name: 'String',
username: 'String',
password: 'String',
email: 'String'
});
return instance;
}
]).service('documentService', [
'ModelFactory', function(modelFactory) {
var instance;
instance = new modelFactory();
instance.register("Document", {
name: 'String',
format: 'String',
fileSize: 'String'
});
return instance;
}
]);
/*
Example Usage
*/
angular.module('controllers', []).controller('exampleController', [
'$scope', 'userService', 'documentService', function($scope, userService, documentService) {
userService.klass();
/*
returns
{
className: "User"
schema: {
name : 'String'
username : 'String'
password: 'String'
email: 'String'
}
}
*/
return documentService.klass();
/*
returns
{
className: "User"
schema: {
name : 'String'
format : 'String'
formatileSize: 'String'
}
}
*/
}
]);
angular 仅提供单例服务/工厂选项。一种解决方法是拥有一个工厂服务,它将在您的控制器或其他消费者实例中为您构建一个新实例。唯一注入的是创建新实例的类。这是注入其他依赖项或根据用户规范初始化新对象(添加服务或配置)的好地方
namespace admin.factories {
'use strict';
export interface IModelFactory {
build($log: ng.ILogService, connection: string, collection: string, service: admin.services.ICollectionService): IModel;
}
class ModelFactory implements IModelFactory {
// any injection of services can happen here on the factory constructor...
// I didnt implement a constructor but you can have it contain a $log for example and save the injection from the build funtion.
build($log: ng.ILogService, connection: string, collection: string, service: admin.services.ICollectionService): IModel {
return new Model($log, connection, collection, service);
}
}
export interface IModel {
// query(connection: string, collection: string): ng.IPromise<any>;
}
class Model implements IModel {
constructor(
private $log: ng.ILogService,
private connection: string,
private collection: string,
service: admin.services.ICollectionService) {
};
}
angular.module('admin')
.service('admin.services.ModelFactory', ModelFactory);
}
然后在您的消费者实例中,您需要工厂服务并在需要时调用工厂的构建方法以获取新实例
class CollectionController {
public model: admin.factories.IModel;
static $inject = ['$log', '$routeParams', 'admin.services.Collection', 'admin.services.ModelFactory'];
constructor(
private $log: ng.ILogService,
$routeParams: ICollectionParams,
private service: admin.services.ICollectionService,
factory: admin.factories.IModelFactory) {
this.connection = $routeParams.connection;
this.collection = $routeParams.collection;
this.model = factory.build(this.$log, this.connection, this.collection, this.service);
}
}
您可以看到它提供了注入一些在工厂步骤中不可用的特定服务的操作。您始终可以在工厂实例上进行注入,以供所有模型实例使用。
注意我必须去掉一些代码,所以我可能会犯一些上下文错误......如果你需要一个有效的代码示例,请告诉我。
我相信 NG2 可以选择在 DOM 中的正确位置注入新的服务实例,因此您无需构建自己的工厂实现。将不得不拭目以待:)
我相信有充分的理由在服务中创建对象的新实例。我们也应该保持开放的心态,而不是仅仅说我们不应该做这样的事情,但是单例是有原因的。控制器通常在应用程序的生命周期内创建和销毁,但服务必须是持久的。
我可以想到一个用例,您有某种工作流程,例如接受付款并且您设置了多个属性,但现在必须更改其付款类型,因为客户的信用卡失败并且他们需要提供不同形式的支付。当然,这确实与您创建应用程序的方式有很大关系。您可以重置支付对象的所有属性,或者您可以在服务中创建对象的新实例。但是,您不会想要新的服务实例,也不会想要刷新页面。
我相信一个解决方案是在服务中提供一个对象,您可以创建一个新实例并进行设置。但是,需要明确的是,服务的单个实例很重要,因为控制器可能会被创建和销毁多次,但服务需要持久性。您正在寻找的可能不是 Angular 中的直接方法,而是您可以在服务中管理的对象模式。
例如,我制作了一个重置按钮。(这没有经过测试,它实际上只是一个在服务中创建新对象的用例的快速想法。
app.controller("PaymentController", ['$scope','PaymentService',function($scope, PaymentService) {
$scope.utility = {
reset: PaymentService.payment.reset()
};
}]);
app.factory("PaymentService", ['$http', function ($http) {
var paymentURL = "https://www.paymentserviceprovider.com/servicename/token/"
function PaymentObject(){
// this.user = new User();
/** Credit Card*/
// this.paymentMethod = "";
//...
}
var payment = {
options: ["Cash", "Check", "Existing Credit Card", "New Credit Card"],
paymentMethod: new PaymentObject(),
getService: function(success, fail){
var request = $http({
method: "get",
url: paymentURL
}
);
return ( request.then(success, fail) );
}
//...
}
return {
payment: {
reset: function(){
payment.paymentMethod = new PaymentObject();
},
request: function(success, fail){
return payment.getService(success, fail)
}
}
}
}]);
这是解决问题的另一种方法,我非常满意,特别是与启用高级优化的 Closure Compiler 结合使用时:
var MyFactory = function(arg1, arg2) {
this.arg1 = arg1;
this.arg2 = arg2;
};
MyFactory.prototype.foo = function() {
console.log(this.arg1, this.arg2);
// You have static access to other injected services/factories.
console.log(MyFactory.OtherService1.foo());
console.log(MyFactory.OtherService2.foo());
};
MyFactory.factory = function(OtherService1, OtherService2) {
MyFactory.OtherService1_ = OtherService1;
MyFactory.OtherService2_ = OtherService2;
return MyFactory;
};
MyFactory.create = function(arg1, arg2) {
return new MyFactory(arg1, arg2);
};
// Using MyFactory.
MyCtrl = function(MyFactory) {
var instance = MyFactory.create('bar1', 'bar2');
instance.foo();
// Outputs "bar1", "bar2" to console, plus whatever static services do.
};
angular.module('app', [])
.factory('MyFactory', MyFactory)
.controller('MyCtrl', MyCtrl);