toggle menu

[AngularJS] 모듈이란 무엇인가?

2012.10.31 09:54 AngularJS
대부분의 어플리케이션은 어플리케이션을 구동하는 시발점이 되는 메인 메소드를 가지고 있다.
하지만 앵귤러 어플리케이션은 메인 메소드를 가지고 있지 않다.
그 대신 어떻게 어플리케이션이 구동되어야 하는지 구체적으로 명시하는 목적으로 모듈이 사용된다.

 
간단한 모듈 사용 예를 살펴보자.
앵귤러JS의 가장 큰 특징 중 하나인 모듈을 사용해서 Hello World 메시지를 띄우는 무척 긴 소스이다.

Plunker
http://plnkr.co/edit/KeGEsFA9Hs1bXXaXtIi3

<!doctype html>
<html ng-app="myApp">
<!-- ng-app="myApp" 을 통해 우리가 만든 모듈을 사용해서 어플리케이션이 구동된다. -->

	<head>
		<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js">
		</script>
		<script type="text/javascript" charset="utf-8">

		// 모듈 선언!
		var myAppModule = angular.module('myApp', []);
		 
		// 모듈 설정
		// 여기에서는 필터링 기능을 추가한다.
		myAppModule.filter('myFilter', 
		function()
		{
			//greet 이라는 이름의 필터를 만들었고 그 기능이 여기에 선언된다.
			
			return function(str)
			{
				return 'Hello, ' + str + '!'; //특정 문자열을 더한 후 반환!
			};
		});
		
		</script>
	</head>
	<body>
		<div>
			{{ 'World' | myFilter }}
		</div>
	</body>
</html>




추천하는 설정
위의 예는 심플한 반면 큰 어플리케이션에 적용될 만큼 확장성이 있지는 않다.
대신에 아래와 같이 다양한 모듈들로 나누어 어플리케이션을 구성하는 것을 추천한다.

1. 서비스 선언을 위한 서비스 모듈
2. 지시어 선언을 위한 지시어 모듈
3. 필터 선언을 위한 필터 모듈
4. 초기화 코드들과 위의 모듈들에 의존적인 어플리케이션 레벨의 모듈들

이렇게 나누는 까닭은 테스트의 용이함을 위해서인데 제안일뿐 너무 얽매일 필요는 없다.



그럼 위에서 권하는 형태로 모듈을 구성한 예를 살펴보자.
프로그래밍 책에서 첫 예제로 많이 사용되는 Hello World! 를 출력하는 어플리케이션이다.


helloworld.html 파일
<!doctype html>
<html ng-app="helloWorld">
	<head>
		<script src="http://code.angularjs.org/angular-1.0.2.min.js"></script>
		<script src="module.js"></script>
	</head>
	<body>
		<div ng-controller="helloWorldController">
			{{ greeting }}!
		</div>
	</body>
</html>



module.js 파일
//지시어 모듈 : 현재 비어 있음
angular.module('helloWorld.directive', []);

//필터 모듈 : 현재 비어 있음
angular.module('helloWorld.filter', []);

//서비스 모듈
angular.module('helloWorld.service', []).
	value('greeter', //greeter 라는 이름의 서비스 등록
	{
		salutation: 'ㅋㅋㅋㅋㅋㅋ', //인사말 기본값
		
		//setSalutation 함수 선언
		setSalutation: function(salutation)
		{
			//파라메터로 들어온 인사말을 내부의 인사말에 대입
			
			//즉, greeter 라는 서비스가 있는데 기본적으로는
			//salutation을 사용하지만, setSalutation 함수를
			//사용하면 인사말을 임의로 수정할 수 있다.
			this.salutation = salutation;
		},
	}).
	value('user', //user 라는 이름의 서비스 등록
	{
		name: 'ㅎㅎㅎㅎㅎㅎ', //이름 기본값
		
		//user 라는 서비스 안에는 load 라는 함수가 존재하는데,
		//setName 함수의 파라메터로 전달된 이름을 name 이라는
		//변수에 저장해둔다.
		setName: function(name) 
		{
			this.name = name;
		}
	}).
	value('printHello', //printHello 라는 이름의 서비스 등록
	{
		print : function(salutation, name)
		{
			return salutation + ' ' + name + '!';
		}
	});

//어플리케이션 모듈 - 서비스, 지시어, 필터를 포함하고 있다(?).
angular.module('helloWorld',
	['helloWorld.service', 'helloWorld.directive', 'helloWorld.filter']).
	run(function(greeter, user) //서비스모듈에 등록된 greeter와  user 서비스를 파라메터로 가져옴
	{
		//여기에서 일종의 초기화가 이루어지고 있다.
		
		//어플리케이션 모듈은 이렇게 이 공간을 초기화를 목적으로
		//사용하게 되는 경우가 많은 걸까?
		
	
		//greeter 서비스에 포함된 setSalutation 함수를 사용해,
		//인사말을 Hello로 설정하고 있다.
		greeter.setSalutation('Hello');
		
		//user 서비스 안에 포함된 setName 함수를 사용해
		//이름을 World로 설정하고 있다.
		user.setName('World');
	});


//어플리케이션의 컨트롤러
function helloWorldController($scope, greeter, user, printHello)
{
	//뷰 상의 {{ greeting }} 부분에 출력될 모델을 결정해준다.
	$scope.greeting = printHello.print(greeter.salutation, user.name);
};​


지시어 모듈과 필터 모듈은 선언만 되어 있고 실상은 아무것도 없는 상태이다.

서비스 모듈에는 greeter와 user, printHello 서비스를 value를 사용해서 등록해주었고,
각각 인사말 설정, 이름 설정, 인사말 출력을 하는 함수를 포함하고 있다.

그리고 전에 어플리케이션 모듈에서는 이들 모듈들을 통합해서 가지고 있는 형태이고,
run 함수를 수행하며 일종의 초기화 작업을 하고 있는 것을 볼 수 있다.

컨트롤러에서는 위에서 등록된 서비스들을 실제로 사용하고 있는데,
greeter와 user에서 각각 저장된 값을 가져온 후 printHello를 사용해 인사말을 {{ greeting }} 영역에 출력해주고 있다.

※특이하게도 module.js 파일을 외부로 빼지 않고 내부에서 스크립트 태그를 열어서 구현할 경우 정상적으로 모듈 등록이 안되는 문제가 있다.




모듈 로딩 및 종속(Module Loading & Dependencies)

모듈은 설정 블럭과 실행 블럭으로 구성되어 있고 부트스트랩(어플 로드) 과정 동안 적용되도록 되어 있다.
모듈의 가장 단순한 구성인 설정 블럭과 실행 블럭에 대해 살펴보자.

설정 블럭(Configuration blocks) - 프로바이더 등록 및 설정 단계 동안 실행된다. 오직 프로바이더들과 상수들만이 설정 블럭에 인젝트될 수 있다.
실행 블럭(Run blocks) - 인젝터가 생성된 후에 실행되며, 어플리케이션을 시동할 때 사용된다. 오직 인스턴스들과 상수들만 실행 블럭에 인젝트될 수 있다. 이는 어플리케이션이 실행되는 동안 추가적으로 시스템이 구성되는 것을 막기 위함이다.

설정 블럭과 실행 블럭으로 구성된 모듈의 예를 살펴보자.
angular.module('myModule', []).
	config(function(injectables) // provider-injector
	{
		//설정 블럭
		//오지 프로바이더들만 인젝트할 수 있다.
	}).
	run(function(injectables) // instance-injector
	{
		//실행 블럭
		//오직 인스턴스들만 인젝트할 수 있다.
	});


설정 블럭의 경우, 설정 블럭에서 처리해야하는 것들을 대체할 수 있는 몇가지 편리한 메서드들이 존재한다.
아래 예에서 원래 구성과 또 대체 가능한 메서드들이 사용되는 것을 비교하며 이해해보자.

angular.module('myModule', []).
	config(function($provide, $compileProvider, $filterProvider)
	{
		$provide.value('a', 123)
		$provide.factory('a', function() { return 123; })
		$compileProvider.directive('directiveName', ...).
		$filterProvider.register('filterName', ...);
	});

//위의 설정 블럭을 각각의 대체 가능한 메서드로 표현하면,

angular.module('myModule', []).
	value('a', 123).
	factory('a', function() { return 123; }).
	directive('directiveName', ...).
	filter('filterName', ...);



설정 블럭은 등록된 순서대로 적용된다. 한가지 예외가 있다면 상수 선언에 대한 것으로, 모든 설정 블럭의 시작 지점에 위치한다.

실행 블럭들
실행 블럭들은 앵귤러 안의 요소들 중에서 메인 메서드에 가장 가까운 녀석들이다. 실행 블럭은 어플리케이션을 구동시키기 위해 실행이 필요한 코드들이다. 이 코드들은 모든 서비스가 설정되고 인젝터가 생성된 뒤에 실행된다. 실행 블럭은 일반적으로 단위 테스트하기 어려운 코드를 포함하고 있고, 이러한 이유로 반드시 독립적인 모듈로 선언되어야 단위 테스트의 압박에서 벗어날 수 있다.

종속
모듈은 종속을 통해 다른 모듈들을 나열할 수 있다. 모듈에 따라 선행 모듈이 로드되는 것이 요구되기도 한다. 한편 선행 모듈의 설정 블럭은 요청 모듈의 설정 블럭보다 먼저 실행된다. 물론 실행 블럭에 있어서도 이것은 마찬가지이다. 각 모듈은 한번만 로드될 수 있고 이는 다른 여러 모듈들이 모두 해당 모듈을 요청해도 그렇다. (싱글톤 방식이란건가?)

비동기적 로딩
모듈들은 $injector를 설정하는 한가지 방법이고, 모듈들은 VM에 스크립트가 로딩되는 중에 아무것도 하지 않는다. 만약 스크립트의 로딩을 다루는 프로젝트가 존재한다면, 앵귤러가 사용될 수 있을지도 모른다. 왜냐하면 VM에 로드되기 까지 모듈들은 아무것도 하지 않기 때문에 스크립트 로더들은 이러한 속성으로 이점을 얻을 수 있고 로딩 프로세스를 병렬화 할 수 있다.


 

AngularJS 관련 포스팅 더보기