adSense 900*70


AngularJs - Proviers 두번째 JavaScript

AngularJs - Proviers 두번째

원문

지난번 AngularJs - Proviers에서는 factory-recipe까지 살펴보았습니다. 이번에는 Service, Provider, Constant등의 recipe를 살펴보겠습니다.

Service Recipe

Javacript 개발자분들은 자주 객체지향 코드를 작성하기 위해 Application(혹은 domain)내에서 사용될 타입을 사용합니다. 그래서 아래의 예제 코드는 UnicornLauncher라는 타입을 사용했고, 해당 타입을 사용해서 unicornLancher service를 생성할 것입니다.

function UnicornLancher(apiToken){
this.lanchedCount = 0;
this.launch = function(){
//remote API를 호출하고 apiToken을 포함시키세요.
...
this.launchCount++;
}
}

그럼 이제 유니콘을 발사할 준비가 되었내요. 그런데 UnicornLauncher는 apiToken을 인자로 받고 있습니다. 즉, 의존관계를 가지고 있습니다. 이런 의존 관계를 해결하기 위해 다음과 같은 코드를 사용해서 apiToken을 UnicornLauncher에게 주입할 수 있습니다.

myApp.factory('unicornLauncher'. ["apiToken", function(apiToken){
return new UnicornLauncher(apiToken);
}]);

그런데 우리는 이미 UnicornLauncher를 작성해 놓았는데, 위와 같은 코드로 변경을 해야 한다면 귀찮죠. 꼭 이렇게 해야하는지 의문도 들고. 그래서 이런 경우에는 바로 service를 사용하는 것이 좋습니다.

Service recipe는 Value 혹은 Factory recipe처럼 서비스 인스턴스를 생성합니다. 그렇지만 차이점은 new 연산자를 사용하여 생성자를 호출한다는 것입니다. 해당 생성자는 인자가 없을 수도, 하나 이상의 인자를 가질 수 도 있습니다. 생성자에 선언된 인자들은 생성자를 통해 생성할 인스턴스가 필요로 하는 의존관계에 있는 객체를 의미합니다.

참고: Service recipe는 생성자 주입이라는 설계 패턴을 사용합니다.

이미 UnicornLauncher를 생성하는 생성자를 정의해 놓았기때문에, 위의 Factory를 사용하는 코드를 아래와 같이 변경할 수 있습니다.

myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);

위의 Factory를 사용한 코드보다 훨씬 간단해졌습니다.

참고: 서비스 recipe를 ‘서비스’라고 이름을 지은것을 후회한다고 합니다. 그 이유가 우리의 자식, 후손을 ‘자식’ 이라고 이름 지은 것과 별반 다르지 않기때문이라내요. 원문에는 it’s like we named one of our offspring ‘Child’ 라고 되어 있습니다. offspring은 낳은 자식이라는 말이고 child와 동의어입니다. 그래서 서비스 recipe를 ‘서비스’라고 이름이은 것은 좀 이상하다는 것이라고 생각할 수 있겠죠

Provider Recipe

이미 처음에 언급을 한것처럼, Provider recipe는 recipe type중에 가장 핵심이 되는 recipe입니다. 그리고 다른 recipe들은 Provider recipe에 기반한 일종의 코딩을 쉽게 하기 위한 방법일 뿐입니다. Provider recipe는 대부분의 능력을 가진 상세한 recipe이긴하지만 좀 과잉된 부분이 있습니다.

Provider recipe는 $get 메서드를 구현한 사용자 정의 타입으로 정의 됩니다. 이 $get메서드는 이전 Factory recipe 예제에서 보았던 것과같은 factory입니다. 사실 Factory recipe를 정의했다면, 내부적으로 구현한 factory함수에 $get 메서드를 설정하는 비어 있는 Provider가 자동으로 생성됩니다.

Factory는 우리말고 공장이죠. 공장에서 동일한 타입의 물건을 마구 찍어내는 것처럼, 여기서 언급된 Factory도 같은 맥락으로 이해하면 됩니다.

application시작전에 application전반에 걸친 설정을 위한 API를 노출시키기를 원할 경우에 Provider recipe를 사용하는 것이 좋습니다. 그리고 application간에 행동이 약간씩 다른 재사용 가능한 서비스들을 생성할때도 사용하면 좋습니다.

자, 그럼 아까 이야기 했던 유니콘으로 돌아가서, unicornLauncher 서비스가 굉장히 인기가 좋아서, 많은 다른앱에서도 사용하기를 원한다고 합니다. 기본적으로 unicornLauncher는 유니콘을 우주로 아무런 보호막도 없이 쏘아 올립니다. 그런데 다른 행성에서는 대기가 아주 두꺼워서 쏘아올릴 유니콘들을 은박지(ㅎㅎ)로 감싸줘야만 합니다. 만약 그렇지 않으면 쏘아올린 유니콘들이 대기를 통과하는 동안 다 터버리게 되는거죠. 그래서 unicornLauncher를 필요로 하는 앱에서 유니콘을 쏘아올릴때를 위해 유니콘을 은박지로 감싸주는 설정을 추가하도록 하겠습니다. 아래의 예제 처럼 말이죠.

myApp.provider('unicornLauncher', function UnicornLauncherProvider(){
var userTinfoilShielding = false;

this.useTinfoilShielding = function(doWrapTinfoil){
userTinfoilShielding = !!doWrapTinfoil;
/*
위의 코드에서 !!는 부정에 부정으로 doWrapTinfoil가 undefined이면 false기때문에 true가 되서 한번더 ! 연산으로 false로 만들어 주기 위한 코드 입니다. 다들 알고 있겠지만...
*/

this.$get = ['apiToken', function unicornLauncherFactory(apiToken){
/*
UnicornLauncher 생성자가 userTinfoilShielding를 설정하기 위한 인자를 받는 다고 가정하자고 합니다.
*/
return new UnicornLauncher(apiToken, userTinfoilShielding);
}];
};
});

자 그럼 유니콘에 은박지를 씌워 볼까요? 그러기 위해선 먼저 module API를 통해 설정 함수(config)를 생성해야 합니다. 그리고 UnicornLauncherProvider를 주입해야 합니다.

myApp.config("unicornLauncherProvider", function(){
unicornLauncherProvider.userTinfoilShielding(true);
});

(참 재미있는 놈들이죠. 공식적인 Guide문서인데, 딱딱하게 말하기보다 말도 안되는 유니콘이야기를 예제로 삼아서 코드 예제를 보여주니 말이죠. 우리도 이렇게 재미있는 생각을 하면서 일하면 좋겠내요. ㅎㅎ)

config함수에 unicorn provider가 주입된것을 유심히 보세요. unicorn provider는 일반적인 인스턴스 injector와는 다른 provider injector에 의해 주입된것입니다. provider injector는 오로지 provider 인스턴스만을 생성하고 주입을 합니다.

application이 부팅(시작)되는 동안 Angular가 모든 서비스들을 생성하기 전에, Angular는 모든 provider들을 설정하고 생성합니다. 이 과정을 구성단계(configuration phase)라고 부릅니다. 이 구성단계동안, 서비스들은 사용가능하지 않습니다. 왜냐면 아직 생성이 되지 않았기 때문이죠.

구성단계가 모두 완료되고 나면, provider에 추가적인 작업을 할 수 없습니다. 그리고 서비스들을 생성하는 과정이 시직이 됩니다. 이 과정을 실행단계(run phase)라고 부릅니다.

Constant Recipe

지금까지 Angular가 life-cycle을 구성단계와 실행단계로 어떻게 나누는지와 config함수를 통해 application에 설정정보를 제공하는 방법에 대해 살펴보았습니다. config함수는 구성단계에서 실행이 되기때문에, 어떠한 서비스도 사용가능하지 않습니다. 심지어 Value recipe를 통해 생성되는 간단한 값에 접근하는 것도 안됩니다.

그런 URL 접두사와 같은 간단한 값들은 아무런 의존 관계나 설정정보를 필요로 하지 않기때문에, 구성단계나 실행단계에서 사용가능하도록 하는 것이 좋을 것입니다. 그것을 가능하게 하는 것이 Constant recipe입니다.

또 유니콘이 등장합니다. 이번에는 unicornLauncher서비스가 구성단계에서 행성 이름이 주어지면 그 행성 이름을 유니콘에 도장으로 찍어준다고 합니다. 그 행성 이름은 application마다 고유하고 application이 실행되는 동안 다른 controller에서도 사용이 됩니다. 아래처럼 상수로 그 행성이름을 정의 할 수 있습니다.

myApp.constant('planetName', 'Greasy Giant');

이렇게 행성이름을 정의한 다음에, 다음과 같이 unicornLauncherProvider를 정의할 수 있습니다.

myApp.config(['unicornLauncherProvider', 'planetName', function(unicornLauncherProvider, planetName){
unicornLauncherProvider.useTinfoilShiedling(true);
unicornLauncherProvider.stampText(planetName); //도장 꽝꽝!!
}]);

Constant recipe는 Value recipe처럼 실행시에도 사용가능한 값을 만들기때문에, controller와 template에서도 사용할 수 있습니다.

myApp.controller('DemoController', ["clientId", "planetName", function DemoController(clientId, planetName){
this.clientId = clientId;
this.planetName = planetName;
}]);
<html ng-app="myApp">
<body ng-controller="DemoController as demo">
Client ID: {{demo.clientId}}
<br>
Planet Name: {{demo.planetName}}
</body>
</html>

Special Purpose Objects

서비스들과는 다른 특별한 목적을 가진 객체들이 존재하는 것을 이미 언급하였습니다. 이 객체들은 플러그인 처럼 framework을 확장하고 Angular에 명시된 인터페이스들을 반드시 구현해야만 합니다. 이런 인터페이스들은 Controller, Directive, Filter 그리고 Animation들입니다.

이런 특별한 목적을 가진 객체들(Controller 객체는 제외)을 생성하는 inject를 위한 명령은 은밀하게 Factory recipe를 사용합니다.

“Planet Name: Greasy Giant”라는 행성이름에 의존관계를 가지느 directive를 생성하는 예제를 만들어 보겠습니다.

myApp.directive('myPlanet', ['planetName', function myPlanetDirectiveFactory(planetName){
//directive 정의 객체
return {
restrict: 'E',
scope: {},
link: function($scope, $element){$element.text('Planet: '+ planetName);}
}
}]);

생성한 directive는 아래와 같이 사용합니다.

<html ng-app="myApp">
<body>
<my-planet></my-planet>
</body>
</html>

Factory recipe를 사용하여, Angualr filter와 animation들을 정의할 수 있습니다. 하지만 controller는 조금 다릅니다. controller를 고유의 목적에 맞는 타입으로 정의해서 생성할 수 있습니다. 그리고 해당 controller생성자에 의존관계를 가지는 객체들을 선언할 수 있습니다. 이 생성자는 module에 등록이 됩니다. 이전 예제에서 작성했던 코드를 다시 보겠습니다.

myApp.controller('DemoController', ['clientId', function DemoController(clientId) {
this.clientId = clientId;
}]);

DemoController는 생성자를 통해 인스턴스로 만들어 집니다. 이 예제에서는 한번뿐이지만 application에서 DemoController의 인스턴스를 필요로 할때마다 인스턴스를 생성하게 됩니다. 서비스들과는 달리, controller는 singleton이 아닙니다. clientId서비스와 같은 서비스들과 함께 해당 생성자는 호출이 되는 것입니다.

참고: 나중에 Angular를 더 보시면 알 수 있겠지만, $controller(‘DemoController’), $filter(‘customFilter’) 이런식으로 controller나 filter들을 사용할 수 있습니다. 그런데 service는 이런식으로 사용할 수 없습니다. $service는 존재하지 않기때문이며, 특정 서비스에 대한 mock객체를 사용할려면 아래와 같이 사용하여야 합니다.

  beforeEach(function(){
module('app');
module(function($provide){
$provide.value('appService', mockAppService);
});
});

beforeEach(inject(function(appService) {
appSvc = appService;
}));

Conclusion

이제 핵심사항을 정리해보겠습니다.


  • injector는 서비스와 그외의 특별한 객체들을 생성하기 위해 recipe들을 사용합니다.
  • Value, Factory, Service, Provider and Constant와 같이 다섯 종류의 recipe들이 존재합니다.
  • Factor와 Service recipe가 가장 많이 사용되는 recipe이며, 둘의 차이는 Factory는 자바스크립트 원시 타입의 객체와 함수를 생성할 수 있는 반면, Service는 사용자 정의 타입의 객체를 생성하는데 사용이 됩니다.
  • Provider recipe가 핵심 recipe이며 다른 recipe들은 Provider recipe를 기반으로한 문법적, 코딩 용이성을 제공하기 위한 것입니다.
  • Provider는 가장 복잡한 recipe입니다. 전역 설정을 위한 재 사용가능한 코드를 만들것이 아니면 사용할 필요가 없습니다.
  • Controller를 제외한 나머지 객체들은 Factory recipe를 통해서 정의 됩니다.


Features / Recipe typeFactoryServiceValueConstantProvider
can have dependenciesyesyesnonoyes
uses type friendly injectionnoyesyes *yes *no
object available in config phasenononoyesyes **
can create functionsyesyesyesyesyes
can create primitivesyesnoyesyesyes

* at the cost of eager initialization by using new operator directly new연산자를 직접 사용함으로 빠른 초기화라는 댓가를 치러야 함.
** 구성단계에서 서비스 객체는 사용불가이지만, provider인스턴스는 사용가능. (unicornLauncherProvider 예제 참조)




덧글

  • 2016/06/27 17:03 # 삭제 답글 비공개

    비공개 덧글입니다.
  • ryukato 2016/07/01 00:56 # 답글

    별말씀을요. 도움이 되었다면 제가 오히려 기분이 좋내요.
댓글 입력 영역


side adsense

adSense 900*70