[service] AngularJS의 비 싱글 톤 서비스

AngularJS는 문서에서 Services가 Singleton이라고 명시합니다.

AngularJS services are singletons

반 직관적으로 module.factory는 Singleton 인스턴스도 반환합니다.

단일 서비스가 아닌 서비스에 대한 사용 사례가 많다는 점을 감안할 때, ExampleService종속성이 선언 될 때마다 다른 인스턴스에 의해 충족 되도록 서비스의 인스턴스를 반환하는 팩토리 메서드를 구현하는 가장 좋은 방법 은 ExampleService무엇입니까?



답변

나는 new이것이 의존성 주입을 분해하기 시작하고 라이브러리가 특히 제 3 자에게 어색하게 행동 할 것이기 때문에 공장에서 가능한 함수를 반환해야한다고 생각하지 않습니다 . 요컨대, 비단 일 서비스에 대한 합법적 인 사용 사례가 있는지 확실하지 않습니다.

동일한 작업을 수행하는 더 좋은 방법은 팩토리를 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를 만들어 시연 할 수 있습니다.


답변

어떤 사용 사례를 만족시키려는 지 잘 모르겠습니다. 그러나 객체의 팩토리 반환 인스턴스를 가질 수 있습니다. 필요에 맞게 수정할 수 있어야합니다.

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

업데이트 됨

비 싱글 톤 서비스에 대한 다음 요청을 고려하십시오 . Brian Ford는 다음과 같이 말합니다.

모든 서비스가 싱글 톤이라는 생각이 새로운 객체를 인스턴스화 할 수있는 싱글 톤 팩토리를 작성하는 것을 막지는 않습니다.

공장에서 인스턴스를 반환하는 그의 예 :

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에서 작업 중입니다. 예제에서 나는 서비스 ( ‘users’, ‘documents’)가 상속하고 잠재적으로 확장하기를 원하는 기본 모델 (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'
            }
        }
     */
  }
]);


답변

angular는 싱글 톤 서비스 / 공장 옵션 만 제공합니다 . 한 가지 방법은 컨트롤러 또는 기타 소비자 인스턴스 내부에 새 인스턴스를 빌드하는 팩토리 서비스를 사용하는 것입니다. 주입되는 유일한 것은 새 인스턴스를 생성하는 클래스입니다. 다른 종속성을 주입하거나 사용자 사양에 맞게 새 개체를 초기화 할 수있는 좋은 장소입니다 (서비스 또는 구성 추가).

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