Angular2 살펴보기

Angular2가 어떻게 동작하는지, 어떻게 구성되어 있는지에 대한 전반적인 구조를 살펴본다.

Hello, World!


위의 예제는 간단하게 만들어 본 Angular2 Hello, World 이다. 도대체가 간단하지가 않아보인다.

C나 Java처럼, printf("Hello, World!"); 등을 기대하셨다면, 조금 유감스러운 이야기다. Angular2는 생각보다 초기 진입장벽이 높을 수도 있기 때문이다.

하지만 본인의 경험상, 그 장벽만 넘으면 굉장히 쉬워진다. (또한 감사하게도 CLI 도구 의 도움으로 그 장벽을 넘기가 훨씬 쉬워졌다.)

동작 구조

Angular2는 설계되어질 때에 단순한 웹페이지, 더 나아가 단일 웹페이지 어플리케이션(SPA), 그보다도 더욱 그 이상으로 설계되었기 때문에 흔히 알고 있는 웹페이지의 동작 방식과는 많이 다르다.

우선적으로 이해할 것은, Angular2에는 최상위 계층 모듈, ‘root module’이 존재하며, 이는 Angular2 어플리케이션의 시작점(Entry Point) 이다. ‘root module’은 일반적으로 AppModule 이라고 칭한다. 비슷한 의미에서 비교를 해보자면 Angular2에서의 ‘root module’은 C나 Java에서의 int main(int argc, char* argv), public static void main(String[] args)와 유사하다.

‘root module’ 안에는 최상위 계층 컴포넌트, ‘root component’이 존재하며, 이는 실질적으로 AppModule이 취할 행동을 정의하는 부분이라 생각하면 된다. ‘root component’는 일반적으로 AppComponent 이라고 칭한다. 윗 줄에서 비교한 것처럼, 이 부분은 C나 Java에서의 main 함수의 본체 부분과 역할이 유사하다.

int main(int argc, char* argv) {
// 이 부분입니다!
}

이제 실질적으로 어떠한 일이 일어나는지, 웹페이지에서 동작하는 Angular2로 범위를 한정시켜서 자세히 알아보자.

index.html

모든 웹페이지는 다음과 같은 순서로 동작한다.

  1. 사용자가 웹브라우저를 통해 웹페이지에 접속
  2. 웹브라우저가 해당 웹페이지에 해당하는 URI, 즉 URL을 읽어서 웹서버에 자원 요청
  3. 웹서버는 요청받은 URL에 해당하는 웹페이지 반환
  4. 웹브라우저는 웹서버로부터 받은 웹페이지를 읽어, HTML을 해석(parse).

일반적인 웹서버의 경우 단순하게 서버 내에 존재하는 HTML 파일을 반환하며, ASP나 JSP, PHP와 같이 서버 사이드에서 렌더링을 하는 서버 사이드 스크립트의 경우 유동적으로 HTML 페이지를 렌더링하여 반환하고, Tomcat이나 Node.js와 같이 어플리케이션 서버의 경우 HTTP 요청받은 URL에 해당하는 자원을 반환한다.


  1. HTML 문서는 위에서부터 아래로 차례로 해석된다.
  2. 브라우저는 HTML을 해석하여 각각의 태그에 해당하는 DOM을 생성, 렌더링한다.

위: <!DOCTYPE html><html> 로 시작하는 문서의 초미
아래: </html> 로 끝나는 문서의 말미

이 동작과정은 웹페이지에서 동작하는 Angular2 어플리케이션도 예외가 아니다. 하지만 Angular2 어플리케이션의 경우는 이런 기본적인 동작 외에도 추가적인 동작을 필요로 하는데, 바로 Angular 어플리케이션 자체가 실행되기 위한 환경을 조성하는 것이다.

zone.js

Angular 어플리케이션을 실행하기 위한 환경을 조성하는 데에 많은 단계가 있지만, 제일 핵심적인 녀석은 바로 이 zone.js 라는 녀석이다. ng-conf에서 열렸던 컨퍼런스의 이 동영상 에서, 대체 Zone 이 뭔지를 잘 설명해준다.

자세한 것은 아무래도 본 동영상을 참조하시는게 좋지만, 본인이 최소한 요약을 하자면 Zone은 어플리케이션이 실행될 문맥을 만들어주고, 글로벌 변수와의 고립을 통해 변수의 범위를 설정하는데 도움을 주는 기능으로, Dart 언어에 존재하는 기능이라고 한다. Zone.js는 그것의 자바스크립트 구현체.

bootstrapModule

위에서 언급한 ‘root module’ 을 시작점으로 설정하는 것으로부터, Angular2 어플리케이션이 동작한다. 해당 부분은 대개 main.ts 파일 내에 존재하며, 이 파일은 건들 일이 거의 없다.

main.ts 파일의 본체를 보면 다음과 같은데,

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

// 어플리케이션 부팅
platformBrowserDynamic().bootstrapModule(AppModule);

첫 줄에서의 platformBrowserDynamic 모듈이 바로 브라우저에서 Angular2를 실행시키기 위한, 플랫폼에 의존적인 부분이며, Dynamic의 의미는 현재 Angular 코드를 브라우저 내에서 JiT(Just-in Time) 컴파일 하고 있다는 의미이다. 이를 더욱 빠르게 실행시키기 위해 미리 컴파일을 해 놓는 AoT(Ahead-of Time) 컴파일을 하고 싶다면 platformBrowserDynamic 모듈 대신에 platformBrowser을 이용해서 어플리케이션을 부팅하면 된다.

AppModule

‘root module’인 AppModule로부터 시작하여 실질적인 개발이 시작된다.

Hello, World 수준에서의 app.module.ts 파일의 본체를 보면 다음과 같은데,

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

NgModule 이라는 장식자(Decorator), BrowserModule 이라는 브라우저에서 실행되기 위해 필요한 모듈을 들여온 후, ‘root component’ 인 AppComponent를 임포트한다.

이후 AppModule 클래스를 생성하고, 외부에서(main.ts 파일에서) 이를 참조하기 위해 export 선언을 한다. 그리고 이 클래스가 Angular 모듈임을 명시하는 장식자인, NgModule 장식자를 이용하여 메타 정보를 추가한다.

장식자는 타 언어에서도 많이 보이는 설계 패턴으로, 자바와 파이썬이 그 중 하나였던걸로 기억한다. 장식자는 @ 기호를 사용하여 기호 다음에 오는 무언가(함수 또는 클래스 등)를 꾸며주는 역할을 한다.

@NgModule의 경우 6가지의 프로퍼티를 리스트 형태로 받아서 클래스에 메타 정보를 추가해주는데, 각각 다음과 같다.

  • declarations: 모듈이 컴포넌트를 인식하기 위해 필요한 정보 제공. AppModule과 같이 ‘root module’에서는 이후에 생성되는 모든 컴포넌트를 이 곳에 추가해 주어야 한다.
  • imports: 다른 모듈을 해당 모듈로 들여올 경우 명시해야 함.
  • exports: 해당 모듈이 다른 모듈에서 imports 될 가능성이 있다면 명시해야 함.
  • providers: 모듈이 서비스를 인식하기 위해 필요한 정보 제공. AppModule과 같이 ‘root module’에 명시될 경우, 명시된 서비스는 특정 컴포넌트에 종속되지 않고 모든 컴포넌트가 해당 서비스를 사용할 수 있음.
  • entryComponents: 어떠한 컴포넌트가 사용자의 행동에 의해 동적으로 생성될 수 있는 경우(ViewContainerRef 등으로) 명시해야 함.
  • bootstrap: 초기화 시킬 컴포넌트 명시. 대개 AppComponent 를 사용하며, 단 하나의 값만 필요로 함.

AppComponent

AppComponent에 대해 언급하기 전에, 잠시 시간을 들여 컴포넌트가 뭔지 대략적으로 알아보자.

Component, 본 문서에서 컴포넌트라 칭하는 녀석은 비유하자면 Angular2의 벽돌과도 같은 녀석이다. 아마 react.js에 익숙하신 분들이나, 최근 웹 동향에 민감하신 분들이라면 이미 아실 수도 있다. 다음을 참조하길 바람.

Angular에서의 컴포넌트는 3가지 요소로 이루어져 있다.

  1. TypeScript: 컴포넌트의 Action을 정의하는 부분
  2. 템플릿 (HTML, …): 컴포넌트의 View를 정의하는 부분
  3. Stylesheet (CSS, SCSS, …): 컴포넌트의 View, UI를 정의하는 부분

‘root component’인 AppComponent. 모든 Angular2 어플리케이션은 ‘root component’에 속한 템플릿을 로딩하는 것으로부터 시작한다.

본 예제에서의 AppComponent의 코드는 다음과 같은데,

import { Component } from '@angular/core';

@Component({
  selector: 'app',
  template: `
    <h1>Hello, World!</h1>
  `
})
export class AppComponent { }

@NgModule과 마찬가지로, @Component 장식자를 이용하여 AppComponent 클래스에게 컴포넌트에 대한 메타 정보를 추가한다. selector, 즉 CSS 선택자의 자식으로 템플릿이 추가되며, ‘root component’의 선택자는 바로 index.html 파일 내에 존재한다 (여기에서는 app이다).

<!-- ... 생략 -->
</head>

<!-- 3. 어플리케이션 로드 -->
  <body>
  	<app>Loading...</app>
  </body>
</html>

모듈 번들러

Angular2가 진입장벽이 높은 이유가 바로 이 녀석 때문이라고 생각한다. 프로젝트를 설정하는 것이 무지하게 길고 복잡하다. 특히 베타버전까지만 해도 Angular2를 시작하기 어려웠던 이유는 바로 이 녀석이기 때문이라 생각한다.

모듈 번들러는 복잡하게 나눠져있는 파일들을 묶어 필요한 파일들만 웹페이지 내에 로드될 수 있도록 도와주는 하나의 시스템이다. 여러 가지의 모듈 번들러가 있지만, Angular2와 사용되는 녀석은 현재 크게 SystemJSWebpack으로 나눠진다. 둘 다 장단점이 있지만, 장점이 더욱 큰 Webpack을 사용하는 것이 좋다. 그 이유로는, 아직까지 HTTP 2가 활발하게 사용되고 있지 않은 지금 시점에서 웹 페이지의 로딩 속도는 HTTP Request의 수에 큰 영향을 미치는데, SystemJS의 경우는 동적 모듈 번들러로써 웹 페이지 내에서 SystemJS가 실행이 되며 의존 파일들을 가져오는 형태이기에 HTTP Request가 필연적으로 많아진다. 하지만 Webpack은 정적 모듈 번들러로써 웹 페이지에서 실행되기 이전에 이미 필요한 파일들끼리 묶어버리기 때문에(번들링 하기 때문에) HTTP Request가 훨씬 적어진다. 비유를 하자면 다음과 같다.

  • 5KB 짜리 파일 200개를 가져옴 (총 1MB): SystemJS
  • 1MB 짜리 파일 1개를 가져옴: Webpack

현재 사용하고 있는 HTTP 1과 HTTP 1.1은 한번의 요청에서 하나의 파일만 가져오고 연결을 끊는 방식이기 때문에, 위의 예에서 SystemJS의 경우는 200번의 연결을 열었다 닫았다 해야 한다. 하지만 Webpack은 단 한번의 연결로써 해결이 되기 때문에, 더 나은 방식인것!

그 외에도 많은 플러그인들이 있다. 무엇보다도 커뮤니티가 많이 사용하고 있고, Angular CLI 도구에서 사용하고 있기 때문에 Webpack에 익숙해지는 것이 좋다. 물론 SystemJS가 뭔지는 알아두면 좋다.