AngularJS 디펜던시 인젝션(DI) 이해하기

객체지향 프로그래밍에서 Dependency Injection (DI) 개념은 아주 중요한데, 개별 객체들 사이에 의존성이 줄어들어야 – 다른 말로 느슨한 결합 (loosely coupled)을 이루거나 – 유지보수 및 확장성, 그리고 테스트 가용성 측면에서 많은 이득을 볼 수 있다. 일반적으로 Java 또는 C# 프로그래밍에서는 아래와 같은 형태로 DI를 구성한다.

위의 코드는 C#으로 구현한 간단한 Web API 콘트롤러이다. 콘트롤러 인스턴스를 초기화할 때, 생성자의 파라미터로서 IProductService 인터페이스를 가진 인스턴스를 주입시킨다. 이렇게 하면 저 콘트롤러는 파라미터로 들어온 인스턴스가 어떻게 내부적으로 구현이 되는지 알 필요가 없다. 따라서, ProductService 클라스에 변경사항이 생겨도 아무런 문제가 되질 않는다.

이런 식으로 DI를 구성하는 것이 일반적인데, AngularJS에서는 독특한 방식으로 DI를 생성한다. 아무래도 자바스크립트라는 스크립트 언어의 특성 때문이 아닐까 싶기도 한데, 이부분은 여기서 논의할 것은 아니니 다른 기회를 이용하도록 하자. AngularJS에서 DI를 구현하는 방법은 상당히 다양하다. 우선 간단한 HTML 코드를 작성해 보자.

diSample이라는 모듈의 sampleController를 통해 총 아홉개의 데이터를 바인딩 시키는 모델이다. 저 콘트롤러를 담은 자바스크립트는 아래와 같다.

이렇게 하면 {{text}} 부분이 TEXT로 바뀌어 나오게 된다. 여기서 DI를 적용시켜보자. 우선 $provide 서비스를 이용하는 방법이다.

$provide 서비스를 이용해서 di1이라는 인스턴스를 생성한다. 그리고 콘트롤러 선언부분을 아래와 같이 수정해 준다.

이렇게 하면 콘트롤러에서 di1() 인스턴스를 호출하게 되면 그 결과가 $scope.di1text1$scope.di1text2에 반영되어 화면에 나타난다. 하지만, 이것보다 좀 더 간단한 방법으로 동일한 결과를 얻을 수 있다. 위의 module.config() 안쪽에 아래와 같은 코드를 넣는다.

이것은 위의 $provide.provider() 펑션을 좀 더 간단하게 한 것으로 $provide.factory() 펑션을 쓰고 있다. 이것을 더욱 간단하게 하면 아래와 같이 $provide.value() 형태로도 쓸 수 있다.

이제 콘트롤러를 아래와 같이 바꾸어보자.

이렇게 콘트롤러를 변경한 후에 결과를 확인해 보면di1, di2, di3 객체가 어떻게 쓰였는지 알 수 있다. 심지어 이보다 더 간단하게 DI를 적용시킬 수도 있다. 위의 예제 코드는 모두 module.config() 안에 $provide 서비스를 포함시킨 후 그 스코프 안에서 DI를 위한 객체들을 생성시켜 놓는 것이었다면, 이것을 좀 더 간단하게 해서 module.provider(), module.factory(), module.value() 형태로도 사용할 수 있다. 아래 코드를 살펴보도록 하자.

차이점을 발견할 수 있는가? 이렇게 만들어 놓은 객체들을 다시 콘트롤러를 수정하여 적용시켜 보자.

이렇게 콘트롤러를 수정한 후 결과를 보면 어떻게 달라졌는지 확인할 수 있을 것이다. 마지막으로 $injector 서비스를 이용하여 DI를 구현하는 방법이다. 위의 콘트롤러를 아래와 같이 수정하자.

이렇게 수정한 후에 다시 결과를 확인해 보도록 하자.

정리하자면, AngularJS에서 DI는 다양한 방법 – 여기서는 총 일곱가지 방법 – 으로 구현이 가능하다. 방법은 서로 다르지만 모두 동일한 결과를 가져온다. 그 중에서 가장 간결한 방법은 module.value() 방법이고, 가장 복잡한 방법은 module.config()$provide 서비스를 이용하여 provider 펑션을 호출하는 것이다. 간결할 수록 개발자가 콘트롤해야 하는 부분이 줄어들고, 복잡할 수록 개발자가 관여해야 하는 부분이 늘어난다. 요구사항의 복잡도에 따라 선택해서 사용하면 될 것이다.

이상으로 간단하게 AngularJS에서 DI를 활용하는 방법에 대해 논의해 보았다. 위의 코드는

http://jsfiddle.net/bluemood/QC9pW/

이곳에서 테스트해 볼 수 있다.

참고:

You might be interested in...

  • 설거지의 달인

    큰 도움이 되었습니다. 감사합니다.

    • 도움이 되셨다니 다행입니다!

  • Gayoung Lee

    가장 간단한 module.value()를 쓰면 되지 않나요? 다른 방법을 쓰는 이유는 뭔가요?

    • 마지막 문단에 보시면 module.value()는 가장 간단한 방법인 반면에 개발자가 개입할 수 있는 여지에 대한 한계가 있고, module.config() 를 쓰면 가장 복잡한 방법이긴 하지만, 개발자가 여러가지 개입할 수 있는 여지가 많이 있다고 하는군요. 개발자의 자유도가 높은가 아닌가에 대한 얘기라고 보시면 될 듯 싶습니다.

  • yehongj

    di 개념을 잘못 이해하고 계신것 같습니다. di는 어떤 객체가 정상동작하기 위해 필요로 하는 종속요소(dependent element)들을 framework에서 만들어 주는 겁니다. angular 관점에서 controller($scope)은 보통이지만, controller($scope,. $http)인 경우 controller가 정상 동작하려면 $http가 필요합니다. 즉 이경우 종속요소는 $http입니다. angular는 app.controller(“controller”, [“$scope”, “$http”, controller]) 와 같이 등록된 controller의 configuration에서 controller가 정상 동작하기 위한 종속요소들을 판단하고, $http와 같은 필요한 종속 요소들을 밀어넣어 줍니다. $provider는 angular.module.directive, module.controller, module.service와 같은 convience method들의 low-level 입니다. 정확히 $provider는 di 와 직접적인 관련은 없습니다. angular di는 directive가 되었던, service, factory, const, controller등이 정상동작하기 위해 필요한 dependency를 $로 시작하는 angular것이면 거기서 찾고, 없으면 사용자가 등록한 다양한 $provider의 convenience 버전인 directive, controller, service 등으로 등록된 이름에서 찾습니다.

    • 다른 관점에서 DI를 보면서 같은 얘기를 하시는 것으로 보입니다. DI의 궁극적인 목적은 말씀하신 바와 같이 특정 객체에 대한 종속성을 줄이기 위한 것이구요, 그게 어디에서 만들어지건 상관 없습니다. 단지 IoC 컨테이너들은 그러한 디펜던시들을 모아서 일괄적으로 관리할 뿐이구요. 그 외에는 딱히 어떤 말씀을 하시려고 하는지 잘 모르겠네요.

      • 글의 제목이 “Angular.js의 $provide의 이해” 쯤 된다면 모르겠는데, 보통의 Angular.js에선 전혀 사용하지 않는 $provide와 $get 부분 코드가 Angular.js에 익숙하지 않은 개발자의 경우 혼란을 줄 수 있는 것 같다는 생각에서 쓴 글입니다. $provide의 설명이면 $injector에 대한 설명이 함께 나와야 할 것 같은데.. 어찌 하였든 제가 글을 너무 날림(?) 으로 읽은 것 같습니다. 죄송합니다.

        • 포커스가 $provider 쪽이 아니고 여러가지 방법으로 DI를 구현할 수 있다에 대한 간단한 예제 쯤 되는 내용이라서 굳이 깊이 들어가지 않았습니다. ㅋ 제목 그대로 Angular.js 에서 DI를 어떻게 구현하나에 가까운 거죠.

  • yehongj

    $provider를 의도적으로 사용하는 open source 개발자들은 자기 과시욕(?) 이 큰 것 같습니다. 1+1 = 2 이러면 될 것을 왜 굳이 1+(2-3+1) + 1 = 2 라고 코드를 작성하는지 모르겠습니다.

    • 그렇게 볼 수도 있겠지만, 뭔가 다른 이유도 있지 않을까요? 본문에 언급했다시피 가장 간편하게 쓸 수 있는 방법은 .value()를 쓰는 것이고, 가장 개발자가 커스터마이징하기 쉬운 방법은 $provider 객체를 쓰는 거니까요. 말씀하신 바와 같이 허세로 일부러 복잡하게 쓰는 사람이 있다면 저라면 같이 일하고 싶지 않을 것 같습니다.

      • 저는 2년 동안 0.8인가의 Angular.js 부터 현재의 1.3 버전까지를 경험했습니다. ngModal, angular-file-upload 등 꽤 많은 라이브러리도 접했는데, $provide로 구현된 건 1-2개 정도 되는 것 같습니다. 그동안 10만 줄 작성했었는데 $provide의 필요성을 느낀 적이 없었습니다. angular module(…).directive(..).service(…).controller(…).factory(…).config(…).run(…) 등 angular module의 6개 메소드 이외에 다른 건 필요성이 별로 없더라구요.

        • ㅇㅇ 저도 개발하면서 굳이 복잡하게 $provider 레벨까지 내려갈 일은 없더라구요. 저같은 쪼렙 개발자는 .value()면 충분;;; 제가 Angular.js 를 공부하면서 이해한 내용들을 정리한 것들이고, 문서에서도 이렇게 DI를 쓴다 하길래 다 정리해 놓은 것들을 뿐입죠.

  • di 는 객체지향언어에서만 사용되는 것은 아닙니다. java/spring framework에서 di 가 popular 되었기 때문일 수 있지만, di는 꼭 spring처럼 reflection을 물리적 장치로 활용하지 않기 때문입니다. scala언어의 매개변수에 대한 implicit conversion이나, angular의 매개변수에 대한 injection도 di 입니다. 이 두 언어는 객체지향 이라기 보다는 함수형 언어입니다.

    • DI가 객체지향언어에서만 쓰인다고 본문에 언급한 적은 없습니다. 예를 들다 보니 자바와 C# 같은 객체지향 언어를 언급한 것이고, 또 그게 제가 주로 사용하는 개발언어이기도 하구요.

      • 저는 “객체지향 프로그래밍에서 Dependency Injection (DI) 개념은 아주 중요한데” 부분을 그렇게 해석했는데, 제가 문구를 잘 못 이해했나봅니다. 혹 맘상하셨으면 죄송합니다.

        • 괜찮습니다. 제가 조금 더 명확하게 쓸 걸 그랬나요? ㅠㅠ

  • haruair

    얘기한 김에 성지순례 왔습니다 ㅋㅋ