AngularJS jasno stwierdza w swojej dokumentacji, że usługi są singletonami:
AngularJS services are singletons
Wbrew intuicji module.factory
zwraca również instancję Singleton.
Biorąc pod uwagę, że istnieje wiele przypadków użycia usług innych niż pojedyncze, jaki jest najlepszy sposób na zaimplementowanie metody fabrycznej w celu zwrócenia wystąpień usługi, tak aby za każdym razem, gdy ExampleService
zadeklarowano zależność, spełniała ją inna instancja ExampleService
?
Odpowiedzi:
Nie sądzę, abyśmy kiedykolwiek przywracali fabryczną
new
funkcję, ponieważ zaczyna to rozkładać wstrzykiwanie zależności, a biblioteka będzie zachowywać się niezręcznie, szczególnie dla osób trzecich. Krótko mówiąc, nie jestem pewien, czy istnieją jakiekolwiek uzasadnione przypadki użycia usług innych niż pojedyncze.Lepszym sposobem osiągnięcia tego samego jest użycie fabryki jako interfejsu API do zwracania kolekcji obiektów z dołączonymi do nich metodami pobierającymi i ustawiającymi. Oto pseudokod pokazujący, jak może działać korzystanie z tego rodzaju usługi:
.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(); }; });
To tylko pseudokod do wyszukiwania widżetu według identyfikatora, a następnie umożliwiający zapisanie zmian wprowadzonych w rekordzie.
Oto pseudokod dla usługi:
.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 // ... }; });
Chociaż nie uwzględniono tego w tym przykładzie, tego rodzaju elastyczne usługi mogą również łatwo zarządzać stanem.
Nie mam teraz czasu, ale jeśli będzie to pomocne, mogę później złożyć prostego Plunkera, aby zademonstrować.
źródło
$resource
.Nie jestem do końca pewien, jaki przypadek użycia próbujesz spełnić. Możliwe jest jednak przywrócenie instancji obiektu do fabryki. Powinieneś móc to zmodyfikować, aby dopasować je do swoich potrzeb.
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
Zaktualizowano
Rozważ następujące żądanie dotyczące usług innych niż pojedyncze . W którym Brian Ford zauważa:
i jego przykład powracających instancji z fabryk:
myApp.factory('myService', function () { var MyThing = function () {}; MyThing.prototype.foo = function () {}; return { getInstance: function () { return new MyThing(); } }; });
Twierdziłbym również, że jego przykład jest lepszy, ponieważ nie musisz używać
new
słowa kluczowego w kontrolerze. Jest zamknięty wgetInstance
metodzie usługi.źródło
ngInfiniteScroll
niestandardową usługą wyszukiwania, więc mogę opóźnić inicjalizację do momentu kliknięcia. JSFiddle of 1st answer zaktualizowane o drugie rozwiązanie: jsfiddle.net/gavinfoley/G5ku5new
jest deklaratywne i łatwo jest od razu powiedzieć, które usługi są singletonami, a które nie. Na podstawie tego, czy obiekt jest nowy.Innym sposobem jest skopiowanie obiektu usługi z rozszerzeniem
angular.extend()
.app.factory('Person', function(){ return { greet: function() { return "Hello, I'm " + this.name; }, copy: function(name) { return angular.extend({name: name}, this); } }; });
a następnie, na przykład, w kontrolerze
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 });
Oto plunk .
źródło
Wiem, że na ten post już odpowiedziano, ale nadal uważam, że byłyby pewne uzasadnione scenariusze, w których trzeba mieć usługę inną niż singleton. Załóżmy, że istnieje logika biznesowa wielokrotnego użytku, którą można współdzielić między kilkoma kontrolerami. W tym scenariuszu najlepszym miejscem do umieszczenia logiki byłaby usługa, ale co zrobić, jeśli musimy zachować jakiś stan w naszej logice wielokrotnego użytku? Następnie potrzebujemy usługi innej niż singleton, aby można ją było udostępniać na różnych kontrolerach w aplikacji. Oto jak wdrożyłbym te usługi:
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)); }]);
źródło
Oto mój przykład usługi innej niż singleton, pochodzi z ORM, nad którym pracuję. W przykładzie pokazuję model podstawowy (ModelFactory), który chcę, aby usługi („użytkownicy”, „dokumenty”) dziedziczyły i potencjalnie rozszerzały.
W moim ORM ModelFactory wstrzykuje inne usługi, aby zapewnić dodatkową funkcjonalność (zapytania, trwałość, mapowanie schematu), która jest piaskownicą za pomocą systemu modułowego.
W tym przykładzie zarówno usługa użytkownika, jak i obsługa dokumentów mają tę samą funkcjonalność, ale mają własne, niezależne zakresy.
/* 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' } } */ } ]);
źródło
kątowy daje tylko pojedynczą usługę / opcję fabryczną. jednym ze sposobów jest posiadanie usługi fabrycznej, która utworzy dla Ciebie nową instancję wewnątrz kontrolera lub innych instancji konsumenckich. jedyną rzeczą, która jest wstrzykiwana, jest klasa, która tworzy nowe instancje. to jest dobre miejsce do wstrzyknięcia innych zależności lub zainicjowania nowego obiektu do specyfikacji użytkownika (dodanie usług lub konfiguracji)
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); }
następnie w instancji konsumenckiej potrzebujesz usługi fabrycznej i wywołaj metodę kompilacji w fabryce, aby uzyskać nową instancję, gdy jej potrzebujesz
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); } }
widać, że zapewnia możliwość wstrzyknięcia określonych usług, które nie są dostępne na etapie fabrycznym. zawsze można wykonać wstrzyknięcie w instancji fabryki, która będzie używana przez wszystkie instancje modelu.
Uwaga Musiałem usunąć część kodu, więc mogłem popełnić błędy kontekstu ... jeśli potrzebujesz próbki kodu, który działa, daj mi znać.
Wierzę, że NG2 będzie miało możliwość wstrzyknięcia nowej instancji Twojej usługi we właściwe miejsce w Twoim DOM, więc nie musisz budować własnej implementacji fabrycznej. będę musiał poczekać i zobaczyć :)
źródło
Uważam, że istnieje dobry powód, aby utworzyć nową instancję obiektu w ramach usługi. Powinniśmy również zachować otwarty umysł, a nie tylko mówić, że nigdy nie powinniśmy robić czegoś takiego, ale singleton powstał w ten sposób z jakiegoś powodu . Kontrolery są tworzone i niszczone często w cyklu życia aplikacji, ale usługi muszą być trwałe.
Przychodzi mi do głowy przypadek użycia, w którym masz pewien przepływ pracy, taki jak akceptowanie płatności i masz ustawionych wiele właściwości, ale musisz teraz zmienić typ płatności, ponieważ karta kredytowa klienta zawiodła i musi podać inną formę Zapłata. Oczywiście ma to wiele wspólnego ze sposobem tworzenia aplikacji. Możesz zresetować wszystkie właściwości obiektu płatności lub możesz utworzyć nową instancję obiektu w usłudze . Ale nie chcesz nowej instancji usługi ani odświeżania strony.
Uważam, że rozwiązaniem jest udostępnienie obiektu w ramach usługi, którego można utworzyć i ustawić nową instancję. Ale żeby było jasne, pojedyncza instancja usługi jest ważna, ponieważ kontroler może być tworzony i niszczony wiele razy, ale usługi wymagają trwałości. To, czego szukasz, może nie być bezpośrednią metodą w Angular, ale wzorcem obiektu, którym możesz zarządzać w swojej usłudze.
Jako przykład zrobiłem przycisk resetowania . (To nie jest testowane, tak naprawdę to tylko krótki pomysł na przypadek użycia do tworzenia nowego obiektu w usłudze.
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) } } } }]);
źródło
Oto inne podejście do problemu, z którego byłem całkiem zadowolony, szczególnie w połączeniu z Closure Compiler z włączonymi zaawansowanymi optymalizacjami:
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);
źródło