2013-05-18 97 views
84

角的说明文件中明确指出,服务是单身:非单服务的角度

Angular services are singletons 

直觉相反,module.factory也返回一个Singleton实例。

鉴于非单身服务有很多用例,实现工厂方法以返回服务实例的最佳方式是什么,以便每次声明一个ExampleService依赖项时,它都会得到满足ExampleService的另一个实例?

+1

假设你能做到这一点,你应该?其他角度开发商不会料到一个dependency-注入工厂一直返回新的实例 –

+1

我想这是一个文档问题,我认为这是不受支持的,因为现在有一种期望,即所有服务都是单身,但我 – Undistraction

回答

42

我不认为我们应该有一个工厂返回一个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来演示。

+0

这真的很有趣。一个例子会非常有帮助。非常感谢。 – Undistraction

+0

这很有趣。看起来它的功能类似于角度“$ resource”。 –

+0

@JonathanPalumbo你是对的 - 非常类似于ngResource。实际上,Pedr和我在另一个问题中以切线方式开始了这个讨论,我建议采取类似于ngResource的方法。对于这样一个简单的例子,手动执行它没有什么好处 - ngResource或[Restangular](https://github.com/mgonto/restangular)可以顺畅运行。但对于不太典型的案例,这种方法是有道理的。 –

73

我不完全确定你想要满足什么用例。但是有可能让工厂返回一个对象的实例。你应该能够修改这个以适应你的需求。

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)); 

}); 

JsFiddle

更新

考虑为non-singleton services以下请求。 Brian Ford在其中写道:

所有服务都是单例的想法并不能阻止你从 编写可以实例化新对象的单例工厂。

和他从工厂返回实例的例子:

myApp.factory('myService', function() { 
    var MyThing = function() {}; 
    MyThing.prototype.foo = function() {}; 
    return { 
    getInstance: function() { 
     return new MyThing(); 
    } 
    }; 
}); 

我也认为他的例子是,由于优越的事实,你不必使用new关键字在你的控制器。它被封装在服务的getInstance方法中。

+0

感谢这个例子,所以没有办法让DI容器满足一个实例的依赖关系,唯一的办法就是让它满足一个提供者的依赖关系,然后可以使用它生成实例? – Undistraction

+0

请参阅后更新。 –

+0

谢谢。我同意,比在服务中使用新服务更好,但我认为它仍然不足。为什么依赖于服务的类知道或关心它提供的服务是或不是Singleton?这两种解决方案都无法将这个事实抽象出来,并且正在推动我认为应该是DI容器内部的东西进入依赖项。在创建服务时,我发现允许创建者决定是否希望将其作为单例提供还是作为单独的实例提供。 – Undistraction

20

另一种方法是复制服务对象与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 
}); 

这里是一个plunk

+0

真的很整洁!你知道这个诡计背后有什么危险吗?毕竟,这只是一个角度而已,所以我想我们应该没问题。尽管如此,制作几十个服务副本听起来有点吓人。 – vucalur

2

这是我的一个非单身服务的例子,它来自一个ORM即时工作。在这个例子中,我展示了一个我希望服务('用户','文档')继承和潜在扩展的基本模型(ModelFactory)。

在我的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' 
      } 
     } 
    */ 
    } 
]); 
7

我知道这篇文章已经被回复,但我仍然认为会有一些合法的情况下,你需要有非单身服务。假设有一些可重用的业务逻辑可以在多个控制器之间共享。在这种情况下,放置逻辑的最佳位置是服务,但如果我们需要在可重用逻辑中保留某些状态,该怎么办?那么我们需要非单身服务,因此可以在应用中的不同控制器之间共享。这是我将如何实现这些服务:

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)); 

    }]); 
+0

这与Jonathan Palumbo的答案非常相似,只是Jonathan用他的“Updated”附录封装了所有内容。 – lukkea

+1

你是说非Singleton服务会持久吗?并应保持状态,有点似乎是相反的方式。 –

0

这里的另一种方法,我还是挺满意的问题,与使用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); 
1

角只给出单身服务/工厂选项。 其中一个方法就是拥有一个工厂服务,它将在您的控制器或其他消费者实例中为您创建一个新实例。 唯一注入的是创建新实例的类。 这是注入其他依赖或新对象初始化为用户的规格(添加服务或配置)

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); 
    } 

    } 

你可以看到它提供了opperatiunity注入不在厂里一步都会有一些特定的服务。 您可以始终在所有Model实例使用的工厂实例上进行注入。

注意我不得不剥去一些代码,所以我可能会犯一些上下文错误... 如果你需要一个代码示例,让我知道。

我相信NG2可以选择在您的DOM的正确位置注入一个新的服务实例,因此您不需要构建自己的工厂实现。将不得不等待,看看:)

+0

不错的做法 - 我想看看$ serviceFactory作为npm包。如果你喜欢我可以建立它并添加你作为贡献者? – IamStalker

1

我相信有一个很好的理由来创建一个服务中的对象的新实例。我们应该保持开放的态度,而不是说我们不应该做这样的事情,但是单身人士是这样做的,原因是。控制器经常在应用程序的生命周期内被创建和销毁,但这些服务必须是持久的。

我可以想象一个用例,其中有某种工作流,例如接受付款并且您设置了多个属性,但现在必须更改他们的付款类型,因为客户的信用卡失败,他们需要提供一种不同的付款方式。当然,这与您创建应用的方式有很大关系。您可以重置付款对象的所有属性,或者您可以在服务内创建对象的新实例。但是,你不会想要一个新的服务实例,也不想刷新页面。

我相信一个解决方案是在服务中提供一个对象,您可以创建一个新的实例并进行设置。但是,要明确一点,服务的单一实例很重要,因为可能会多次创建和销毁控制器,但服务需要持久性。你正在寻找的可能不是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) 
      } 
     } 
    } 
}]);