Услуги Non-Singleton в Angular

Angular четко указывает в своей документации, что службы - это синглеты:

Angular services are singletons

Противоинтуитивно, module.factory также возвращает экземпляр Singleton.

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

Ответ 1

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

Лучший способ сделать то же самое - использовать factory как 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, чтобы продемонстрировать.

Ответ 2

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

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

Обновление

Рассмотрим следующий запрос не-singleton-сервисов. В котором Брайан Форд отмечает:

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

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

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

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

Ответ 3

Другим способом является копирование объекта службы с помощью 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.

Ответ 4

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

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

    }]);

Ответ 5

Здесь мой пример службы non singleton, это от 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' 
            }
        }
     */
  }
]);

Ответ 6

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

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

}

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

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

  }

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

Примечание. Мне пришлось отключить некоторый код, чтобы я мог сделать некоторые контекстные ошибки... если вам нужен образец кода, который работает, дайте мне знать.

Я считаю, что NG2 будет иметь возможность внедрить новый экземпляр вашей службы в нужное место в вашем DOM, поэтому вам не нужно создавать собственную реализацию factory. вам придется подождать и посмотреть:)

Ответ 7

Я считаю, что есть хорошая причина для создания нового экземпляра объекта внутри службы. Мы должны также держать открытый ум, а не просто сказать, что мы никогда не должны делать такого, но singleton был создан именно по этой причине. Контроллеры создаются и уничтожаются часто в течение жизненного цикла приложения, но службы должны быть постоянными.

Я могу представить пример использования, когда у вас есть какой-то рабочий процесс, например, принятие платежа, и у вас есть несколько свойств, но теперь вы должны изменить свой тип оплаты, потому что кредитная карта клиента потерпела неудачу, и им необходимо предоставить другой способ оплаты. Конечно, это имеет много общего с тем, как вы создаете свое приложение. Вы могли бы reset все свойства для объекта платежа, или вы могли бы создать новый экземпляр объекта в службе. Но вам не нужен новый экземпляр службы, и вы не хотите обновлять страницу.

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

В качестве примера у меня есть кнопка reset. (Это не проверено, это действительно просто краткое представление об использовании для создания нового объекта в службе.

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

Ответ 8

Вот еще один подход к проблеме, который меня вполне устраивает, особенно при использовании в сочетании с 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);