AngularJS의 비 싱글 톤 서비스


90

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

AngularJS services are singletons

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

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


1
당신이 이것을 할 수 있다고 가정하면 그럴까요? 다른 Angular 개발자는 종속성 주입 팩토리가 항상 새 인스턴스를 반환 할 것으로 기대하지 않습니다.
Mark Rajcok

1
문서화 문제라고 생각합니다. 모든 서비스가 싱글 톤이 될 것이라는 기대가 있기 때문에 이것이 게이트 밖에서 지원되지 않은 것이 부끄러운 일이지만, 싱글 톤으로 제한 할 이유가 없다고 생각합니다.
Undistraction 2013 년

답변:


44

나는 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를 만들어 시연 할 수 있습니다.


이것은 정말 흥미 롭습니다. 예가 정말 도움이 될 것입니다. 감사합니다.
Undistraction 2013 년

이것은 흥미 롭다. angular와 유사한 기능을하는 것 같습니다 $resource.
Jonathan Palumbo

@JonathanPalumbo 맞아요-ngResource와 매우 유사합니다. 실제로 Pedr과 저는 ngResource와 유사한 접근 방식을 제안한 다른 질문에서 접선 적으로이 토론을 시작했습니다. 이렇게 간단한 예의 경우 수동으로 수행하는 것에는 이점이 없습니다. ngResource 또는 Restangular원활 하게 작동합니다. 그러나 완전히 일반적이지 않은 경우에는이 접근 방식이 합리적입니다.
Josh David Miller

4
@Pedr 죄송합니다, 나는 이것을 잊었습니다. 다음은 매우 간단한 데모입니다. plnkr.co/edit/Xh6pzd4HDlLRqITWuz8X
Josh David Miller

15
@JoshDavidMiller 당신은 왜 / 무엇이 "종속성 주입을 깨뜨리고 라이브러리가 어색하게 행동 할 것인가"를 지정할 수 있습니까?
okigan

77

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

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서비스 메서드 내에 캡슐화됩니다 .


예를 들어 주셔서 감사합니다. 따라서 DI 컨테이너가 인스턴스와의 종속성을 충족하도록 할 방법이 없습니다. 유일한 방법은 인스턴스를 생성하는 데 사용할 수있는 공급자와의 종속성을 충족시키는 것입니다.
Undistraction 2013 년

감사. 나는 서비스에서 새로운 것을 사용하는 것보다 낫다는 것에 동의하지만 여전히 부족하다고 생각합니다. 서비스에 의존하는 클래스가 제공되는 서비스가 Singleton인지 아닌지 알고 있거나 관심을 가져야하는 이유는 무엇입니까? 이 두 솔루션 모두이 사실을 추상화하지 못하고 DI 컨테이너 내부에 있어야한다고 생각하는 것을 종속 항목으로 밀어 넣고 있습니다. 귀하가 서비스를 생성 할 때 제작자가 서비스를 싱글 톤으로 제공할지 또는 개별 인스턴스로 제공할지 여부를 결정할 수 있도록 허용하는 것이 해로움을 알고 있습니다.
Undistraction 2013 년

+1 매우 도움이됩니다. 이 접근 방식과 ngInfiniteScroll사용자 지정 검색 서비스를 사용하여 클릭 이벤트가 발생할 때까지 초기화를 지연 할 수 있습니다. 두 번째 솔루션으로 업데이트 첫번째 대답 JSFiddle : jsfiddle.net/gavinfoley/G5ku5
GFoley83

4
새 연산자를 사용하는 것이 왜 나쁜가요? 목표가 싱글 톤이 아닌 경우 사용하는 new것이 선언적이며 어떤 서비스가 싱글 톤인지 아닌지 즉시 알 수있는 것 같습니다. 객체가 새로워 지는지 여부를 기반으로합니다.
j_walker_dev

질문이 요청한 내용, 특히 "업데이트 된"부록을 전달하기 때문에 이것이 답이 될 것 같습니다.
lukkea 2015

20

또 다른 방법은 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.extend'ing 객체이기 때문에 우리는 괜찮을 것 같습니다. 그럼에도 불구하고 서비스 사본을 수십 개 만드는 것은 약간 겁이 날 것 같습니다.
vucalur

9

이 게시물에 대한 답변은 이미 받았지만 싱글 톤이 아닌 서비스가 필요한 몇 가지 합법적 인 시나리오가있을 것이라고 생각합니다. 여러 컨트롤러간에 공유 할 수있는 재사용 가능한 비즈니스 로직이 있다고 가정 해 보겠습니다. 이 시나리오에서 로직을 배치하는 가장 좋은 장소는 서비스이지만 재사용 가능한 로직에 일부 상태를 유지해야하는 경우 어떻게해야할까요? 그런 다음 앱의 여러 컨트롤러에서 공유 할 수 있도록 단일 서비스가 아닌 서비스가 필요합니다. 이것이 내가 이러한 서비스를 구현하는 방법입니다.

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

    }]);

이것은 Jonathan Palumbo의 답변과 매우 유사하지만 Jonathan이 그의 "Updated"부록으로 모든 것을 요약합니다.
lukkea 2015

1
Singleton이 아닌 서비스가 지속적이라는 말입니까? 그리고 상태를 유지해야합니다.
eran otzap 2015

2

다음은 비 싱글 톤 서비스의 예입니다. 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' 
            }
        }
     */
  }
]);

1

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의 올바른 위치에 서비스의 새 인스턴스를 삽입 할 수있는 옵션이 있으므로 자체 공장 구현을 구축 할 필요가 없습니다. 기다려야 할 것입니다 :)


좋은 접근 방식-$ serviceFactory를 npm 패키지로보고 싶습니다. 원하는 경우 구축하여 기여자로 추가 할 수 있습니까?
IamStalker 2016

1

서비스 내에서 개체의 새 인스턴스를 만들어야하는 타당한 이유가 있다고 생각합니다. 우리는 결코 그런 일을하지 말아야한다고 말하는 것보다 열린 마음을 가져야하지만, 싱글 톤은 이유 때문에 그렇게 만들어졌습니다 . 컨트롤러는 앱의 수명주기 내에서 자주 생성 및 삭제되지만 서비스는 영구적이어야합니다.

결제 수락과 같은 일종의 워크 플로가 있고 여러 속성이 설정되어 있지만 고객의 신용 카드가 실패하고 다른 형식을 제공해야하므로 이제 결제 유형을 변경해야하는 사용 사례를 생각할 수 있습니다. 지불. 물론 이것은 앱을 만드는 방법과 많은 관련이 있습니다. 결제 개체의 모든 속성을 재설정하거나 서비스 내에서 개체의 새 인스턴스를 만들있습니다 . 그러나 서비스의 새 인스턴스를 원하지 않거나 페이지를 새로 고치고 싶지 않습니다.

솔루션이 서비스 내에서 새 인스턴스를 만들고 설정할 수있는 개체를 제공한다고 생각합니다. 그러나 명확하게 말하면 컨트롤러가 여러 번 생성되고 파괴 될 수 있지만 서비스에는 지속성이 필요하기 때문에 서비스의 단일 인스턴스가 중요합니다. 찾고있는 것은 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)
            }
        }
    }
}]);

0

특히 고급 최적화가 활성화 된 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);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.