Услуги без синглтона в AngularJS


AngularJS четко указывает в своей документации, что услуги являются Синглетами:

AngularJS services are singletons

парадоксально, module.factory также возвращает одноэлементный экземпляр.

учитывая, что существует множество вариантов использования для не-одноэлементных служб, каков наилучший способ реализации метода factory для возврата экземпляров службы, так что каждый раз ExampleService зависимость объявлена, она удовлетворяется другим экземпляром ExampleService?

8 86

8 ответов:

Я не думаю, что у нас когда-нибудь будет заводское возвращение 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();
  };
});

Это просто псевдо-код для поиска виджета по идентификатору, а затем возможность сохранять изменения, внесенные в запись.

вот некоторые псевдо-код для службы:

.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 позже демонстрировать.

Я не совсем уверен, какой вариант использования вы пытаетесь удовлетворить. Но можно иметь заводские возвращаемые экземпляры объекта. Вы должны быть в состоянии изменить это в соответствии с вашими потребностями.

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. В которой Брайан Форд отмечает:

идея о том, что все службы являются синглетами, не останавливается ты откуда? написание одноэлементных фабрик, которые могут создавать экземпляры новых объектов.

и его пример возврата экземпляров с заводов:

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

Я бы также утверждал, что его пример превосходит из-за того, что вам не нужно использовать new ключевое слово в вашем контроллере. Он инкапсулируется в getInstance метод сервиса.

другой способ-скопировать объект сервиса с помощью 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.

Я знаю, что на этот пост уже был дан ответ, но я все еще думаю, что будут некоторые законные сценарии, которые вам нужно иметь не одноэлементную службу. Допустим, есть несколько многоразовых бизнес-логики, которые могут быть разделены между несколькими контроллерами. В этом случае лучше всего поместить логика, но что если нам нужно сохранить некоторое состояние в наши многоразовые логика? Тогда нам нужен не одноэлементный сервис, поэтому его можно использовать для разных контроллеров в приложении. Вот как бы я поступил реализуйте эти услуги:

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 im работает. В примере я показываю базовую модель (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' 
            }
        }
     */
  }
]);

угловой только дает синглтон сервис/фабрика вариант. один из способов обойти это-иметь заводскую службу, которая построит новый экземпляр для вас внутри вашего контроллера или других экземпляров потребителя. единственное, что вводится-это класс, который создает новые экземпляры. это хорошее место для внедрения других зависимостей или инициализации нового объекта в спецификацию пользователя (добавление служб или конфигурации)

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

вот еще один подход к проблеме, который меня вполне устраивал, особенно при использовании в сочетании с компилятором закрытия с включенной расширенной оптимизацией:

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