service - delegate - Servicios no Singleton en AngularJS
factory angularjs example (8)
AngularJS establece claramente en su documentación que los Servicios son Singletons:
AngularJS services are singletons
Contraintuitivamente, module.factory
también devuelve una instancia de Singleton.
Dado que hay muchos casos de uso para servicios no únicos, ¿cuál es la mejor manera de implementar el método de fábrica para devolver instancias de un servicio, de modo que cada vez que se ExampleService
una dependencia de ExampleService
, se satisfaga con una instancia diferente de ExampleService
?
Aquí está mi ejemplo de un servicio que no es singleton. Es de un ORM en el que trabajo. En el ejemplo, muestro un Modelo base (ModelFactory) que deseo que los servicios (''usuarios'', ''documentos'') hereden y se extiendan.
En mi ORM, ModelFactory inyecta otros servicios para proporcionar una funcionalidad adicional (consulta, persistencia, mapeo de esquema) que se realiza en un espacio aislado utilizando el sistema de módulos.
En el ejemplo, tanto el servicio del usuario como el del documento tienen la misma funcionalidad, pero tienen sus propios ámbitos independientes.
/*
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''
}
}
*/
}
]);
Aquí hay otro enfoque del problema con el que estaba bastante satisfecho, específicamente cuando se usa en combinación con Closure Compiler con optimizaciones avanzadas habilitadas:
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);
Creo que hay buenas razones para crear una nueva instancia de un objeto dentro de un servicio. Deberíamos mantener la mente abierta también en lugar de solo decir que nunca deberíamos hacer tal cosa, pero el singleton fue hecho de esa manera por una razón . Los controladores se crean y destruyen a menudo dentro del ciclo de vida de la aplicación, pero los servicios deben ser persistentes.
Puedo pensar en un caso de uso en el que tiene un flujo de trabajo de algún tipo, como aceptar un pago y tiene varias propiedades establecidas, pero ahora debe cambiar su tipo de pago porque la tarjeta de crédito del cliente falló y deben proporcionar una forma diferente de pago. Por supuesto, esto tiene mucho que ver con la forma en que creas tu aplicación. Puede restablecer todas las propiedades para el objeto de pago, o puede crear una nueva instancia de un objeto dentro del servicio . Sin embargo, no desearía una nueva instancia del servicio ni desea actualizar la página.
Creo que una solución es proporcionar un objeto dentro del servicio que puede crear una nueva instancia de y establecer. Pero, para ser claros, la instancia única del servicio es importante porque un controlador puede crearse y destruirse muchas veces, pero los servicios necesitan persistencia. Lo que está buscando puede no ser un método directo dentro de Angular, sino un patrón de objeto que puede administrar dentro de su servicio.
Como ejemplo, he hecho un botón de reinicio . (Esto no se ha probado, es realmente solo una idea rápida de un caso de uso para crear un nuevo objeto dentro de un servicio.
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)
}
}
}
}]);
No creo que debamos tener una fábrica que devuelva una new
función capaz ya que esto comienza a romper la inyección de dependencia y la biblioteca se comportará de manera torpe, especialmente para terceros. En resumen, no estoy seguro de que haya casos de uso legítimos para los servicios no únicos.
Una mejor manera de lograr lo mismo es utilizar la fábrica como una API para devolver una colección de objetos con los métodos getter y setter adjuntos. Aquí hay un pseudo código que muestra cómo podría funcionar ese tipo de servicio:
.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();
};
});
Esto es solo un pseudocódigo para buscar un widget por ID y luego poder guardar los cambios realizados en el registro.
Aquí hay un pseudo-código para el servicio:
.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
// ...
};
});
Aunque no se incluye en este ejemplo, este tipo de servicios flexibles también podría administrar fácilmente el estado.
No tengo tiempo en este momento, pero si es útil, puedo armar un simple Plunker más tarde para demostrar.
No estoy del todo seguro de qué caso de uso estás tratando de satisfacer. Pero es posible tener instancias de devolución de fábrica de un objeto. Debería poder modificar esto para satisfacer sus necesidades.
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));
});
Actualizado
Considere la siguiente solicitud de servicios no únicos . En el cual Brian Ford señala:
La idea de que todos los servicios sean únicos no le impide escribir fábricas únicas que puedan crear instancias de objetos nuevos.
y su ejemplo de devolver instancias de fábricas:
myApp.factory(''myService'', function () {
var MyThing = function () {};
MyThing.prototype.foo = function () {};
return {
getInstance: function () {
return new MyThing();
}
};
});
También argumentaría que su ejemplo es superior debido al hecho de que no tiene que usar la palabra clave new
en su controlador. Está encapsulado dentro del método getInstance
del servicio.
Otra forma es copiar el objeto de servicio con angular.extend()
.
app.factory(''Person'', function(){
return {
greet: function() { return "Hello, I''m " + this.name; },
copy: function(name) { return angular.extend({name: name}, this); }
};
});
y luego, por ejemplo, en tu controlador
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
});
Aquí hay un plunk .
Sé que esta publicación ya se ha respondido, pero sigo creyendo que habría algunos escenarios legítimos que necesitas para tener un servicio que no sea singleton. Digamos que hay una lógica comercial reutilizable que se puede compartir entre varios controladores. En este escenario, el mejor lugar para poner la lógica sería un servicio, pero ¿qué pasa si necesitamos mantener algún estado en nuestra lógica reutilizable? Entonces necesitamos un servicio que no sea singleton para que se pueda compartir entre diferentes controladores en la aplicación. Así es como implementaría estos servicios:
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));
}]);
angular solo da una opción de servicio único / fábrica. Una forma de evitarlo es tener un servicio de fábrica que construirá una nueva instancia para usted dentro de su controlador u otras instancias del consumidor. lo único que se inyecta es la clase que crea nuevas instancias. este es un buen lugar para inyectar otras dependencias o para inicializar su nuevo objeto a la especificación del usuario (agregando servicios o configuración)
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);
}
luego, en su instancia de consumidor necesita el servicio de fábrica y llama al método de compilación de fábrica para obtener una nueva instancia cuando la necesite
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);
}
}
puede ver que proporciona la oportunidad de inyectar algunos servicios específicos que no están disponibles en el paso de fábrica. siempre puede tener la inyección en la instancia de fábrica para ser utilizada por todas las instancias del Modelo.
Tenga en cuenta que tuve que quitar algún código para que pudiera cometer algunos errores de contexto ... si necesita una muestra de código que funcione, hágamelo saber.
Creo que NG2 tendrá la opción de inyectar una nueva instancia de su servicio en el lugar correcto en su DOM, por lo que no necesita construir su propia implementación en fábrica. Tendré que esperar y ver :)