욱'S 노트

Angular2 - 아키텍처 본문

Programming/Angular2

Angular2 - 아키텍처

devsun 2016. 10. 14. 10:22


Angular는 HTML과 JavaScript나 JavaScript로 컴파일될 수 있는 다른 언어(Dart or TypeScript)로된 클라이언트 어플리케이션을 생성하기 위한 프레임워크이다. 

프레임워크는 몇몇의 라이브러리로 구성되는데, 코어와 몇몇의 옵션 라이브러리들이다.

당신은 Angularized 마크업으로 HTML 템플릿을  구성하고 그러한 템플릿을 관리하기 위해 컴포넌트 클래스를 작성하고 서비스의 어플리케이션 로직을 추가하고 모듈내의 서비스와 컴포넌트를 boxing한다.

그렇게하면 루트 모듈이 기동되어 당신의 어플리케이션이 기동된다. Angular는 당신의 어플리케이션 컨텐츠를 브라우저에 표시하고 당신이 제공한 지시에 따라 사용자 상호작용에 응답한다.

물론 이것보다 더 많은 것들이 있다. 자세한 내용은 아래의 페이지에서 알 수 있다. 큰 그림에 집중하자.

아키텍처 다이어그램을 살펴보면 Angular 어플리케이션은 8개의 주요 구성 요소로 이루어진다는 것을 알 수 있다.

Modules, Components, Templates, Metadata, Data binding, Directives, Services, Dependency Injection

Modules

Angular 앱은 모듈이며 Angular는 자기 자신만의 Angular modules 또는 NgModules라고 불리우는 모듈 구조 시스템을 가지고 있다.

Angular modules은 큰 매우 중요하다. 이 페이지는 모듈들을 소개한다.

모든 Angular 냅은 하나 이상의 모듈을 가지는데 이것은 root module이며 관례상 AppModule이라고 한다.

작은 어플리케이션에서는 루트 모듈 하나로 구성될 수 있을수도 있는 반면에 대부분은 어플리케이션 도메인, 워크플로우, 밀접한 연관된 기능들로 구성된 응집력있는 코드들이 결합하여 구성된다.

Angular 모듈은 @NgModule 데코레이터와 함께 표현된 클래스이다.

데코레이터는 JavaScript 클래스를 변경하는 함수이다. Angular는 많은 데코레이터를 클래스의 metadata로 붙인다. 이러한 클래스들의 의미와 어떻게 동작하는지는 인터넷에 찾아보기 바란다.

NgModule은 데코레이터 함수이다. 이는 모듈을 묘사하는 프로퍼티들로 구성된 하나의 메타데이터 객체를 가진다. 중요한 프로퍼티들은 다음과 같다.

  • declarations - 이 모듈에 속한 view 클래스, Angular는 3가지 종류의 view 클래스를 가진다. components, directives 그리고 pipes

  • exports - declarations의 부분집합. 다른 모듈의 컴포넌트에게 템플릿을 사용하고 보이게 하기위해 사용된다.

  • imports  - 다른 모듈에서 export된 클래스를 활용하기 위해서 사용된다.

  • providers - 서비스의 생성자. 이 모듈은 서비스의 글로벌 컬렉션에서 사용될 수 있다. 이 말은 앱의 모든 파트에서 접근가능하게 된다.

  • bootstrap - 메인 어플리케이션 뷰, 루트 컴포넌트라고 불리운다. 다른 앱 뷰들을 호스트한다. 루트 모듈만 bootstrap 프로퍼티를 세팅할 수 있다.

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
  imports:      [ BrowserModule ],
  providers:    [ Logger ],
  declarations: [ AppComponent ],
  exports:      [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

AppComponent의 export는 단지 export를 어떻게 하는지를 보여준다. 이 예제에서는 사실 필요가 없다. 루트 모듈은 다른 컴포넌트에 export될 필요가 없기 때문이다.NgModule은 데코레이터 함수이다. 이는 모듈을 묘사하는 프로퍼티들로 구성된 하나의 메타데이터 객체를 가진다. 중요한 프로퍼티들은 다음과 같다.

루트 모듈을 구동함으로써 어플리케이션이 구동된다. 개발을 하는 동안에는 기동하기 쉽게 main.ts 파일에 다음과 같이 작성할수도 있다.

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

platformBrowserDynamic().bootstrapModule(AppModule);


Angular Modules vs Javscript Modules

@NgModule로 데코레이트된 클래스를 Angular module이라고 한다. Angular 모듈은 Angular의 가장 기본적인 특징이다. JavaScript 또한 JavaScript 객체를 관리하는 자기자신만의 모듈 시스템을 가진다. 이것은 Angular의 모듈 시스템과 완전히 다르고 관련이 없다.

JavaScript에서 각 파일은 모듈과 그 모듈에 관련된 객체들을 정의할 수 있다. 모듈은 export 키워드와 함께 공용으로 노출될 수 있다. 다른 JavaScript 모듈은 import 문장을 통해 다른 모듈의 public 객체들에 접근할 수 있다. 

두개의 모듈은 완벽하게 다르며 앱을 위해서 둘다 사용할 수 있다.

Angular libraries

Angular는 JavaScript 모듈의 집합들을 탑재한다. 이러한 것들을 libarary 모듈이라고 생각하면 된다.

각 Angular 라이브러리는 @angular라는 접두어로 시작한다.

라이브러리들은 npm을 통해 설치되며 JavaScript import 문장으로 import할 수 있다.

예를 들자면 Angular의 컴포넌트 데코레이터를 import하기 위해선 @angular/core 라이브러리를 사용해야하는데 다음과 같다.

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


또한 Angular 모듈을 Angular 라이브러리로 부터 import 하기 위해선 다음과 같다.

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


위의 단순한 루트 모듈을 보면 어플리케이션 모듈은 BrowserModule를 필요로 한다. 해당 모듈에 접근하기 위해서는 @NgModule 메타데이터에 imports 를 다음과 같이 한다. 

imports:      [ BrowserModule ],


이러한 방법으로 Angular와 JavaScript 모듈 시스템을 같이 활용할 수 있다.

두 시스템은 쉽게 혼동할 수 있는데 공통된 단어 imports와 exports를 사용하기 때문이다. 

Components

컴포넌트는 view라고 불리는 스크린의 조각이다. 

우리는 컨포넌트의 클래스 내부에 어플리케이션 로직을 정의한다. 클래스는 API의 프로퍼티나 메소드를 통해 뷰와 상호작용을 한다. 

예를들어 HeroListComponent는 heroes라는 프로퍼티를 배열로 가지고 있고 이것은 서비스를 통해 획득한다. HeroListComponent 또한 selectHero라는 메소드를 가지고 있는데 사용자가 리스트에서 히어로를 클릭했을때 selectedHero 세팅하는 역할을 담당한다. 

export class HeroListComponent implements OnInit {
  heroes: Hero[];
  selectedHero: Hero;

  constructor(private service: HeroService) { }

  ngOnInit() {
    this.heroes = this.service.getHeroes();
  }

  selectHero(hero: Hero) { this.selectedHero = hero; }
}


Angular는 유저의 이동에 따라 컴포넌트를 생성하고 업데이트하고 소멸한다. 앱은 이러한 라이프사이클에 대한 각 순간을 획득할 수 있는데 이것을 lifecycle hooks이라고 한다. 위에 정의된 ngOnInit() 같은 메소드가 이러한 시점에 반응한다.

Templates

우리는 tempate이라는 곳에 component의 뷰를 정의한다.  template은 Angular에게 어떻게 컴포넌트를 랜더링할지를 알려준다.

template은 몇몇의 차이점을 제외하고는 HTML처럼 보인다. 다음은 HeroListComponent의 템플릿이다.

  1. <h2>Hero List</h2>
  2. <p><i>Pick a hero from the list</i></p>
  3. <ul>
  4. <li *ngFor="let hero of heroes" (click)="selectHero(hero)">
  5. {{hero.name}}
  6. </li>
  7. </ul>
  8. <hero-detail *ngIf="selectedHero" [hero]="selectedHero"></hero-detail>

비록 template은 전통적인 HTML 요소를 사용하고 있지만 또한 몇가지 차이점도 가지고 있다. *ngFor, {{hero.name}}, (click), [hero] 그리고 <hero-detail>과 같은 Angular의 템플릿 문법이 그것이다.

마지막 라인에 <hero-detail> 태그는 HeroDetailComponent의 custom 엘리먼트를 의미한다.

HeroDetailComponent는 HeroListComponent와는 다른 컴포넌트이다.

Metadata

Metadata는 Angular에게 클래스를 어떻게 처리해야 되는지 알려준다. HeroListComponet 코드로 돌아가보면 클래스임을 알 수 있다. 프레임워크의 흔적은 없다.

사실 HeroListComponent는 단지 클래스이다. 이건 Angular에게 말하기 전까지는 component가 아니다.

HeroListComponent가 component라고 Angular에게 알리기 위해선 클래스에 metadata를 붙여야한다.

TypeScript에서 decorator를 사용해 metadata를 붙인다. 다음이 HeroListComponent에 대한 메타데이터이다.

@Component({
  moduleId: module.id,
  selector:    'hero-list',
  templateUrl: 'hero-list.component.html',
  providers:  [ HeroService ]
})
export class HeroListComponent implements OnInit {
/* . . . */
}


@Component 데코레이터가 이 역할을 한다. 

@Component 데코레이터는 Angular가 컴포넌트를 생성하고 view로서 표현하기 위한 필요로하는 설정 정보를 가지고 있다. 

다음이 @Component 설정 가능한 옵션이다.

  • moduleId : 소스셋의 기본 주소. templateUrl과 같은 모듈 연관 URL들에서 사용된다. 

  • selector : CSS selector는 Angular에게 이 컴포넌트를 parent HTML에서 <hero-list>라는 태그로 찾을 수 있게 생성 입력하라고 알려준다. 예를 들어 app의 HTML이 <hero-list>를 포함하고 있다면, Angular는 해당 태그에 HeroListComponent를 삽입한다.

  • templateUrl : HTML 템플릿의 컴포넌트의 모듈 연관 주소이다.

  • providers : 컴포넌트가 필요하는 서비스들의 dependency injection provider들의 배열. Angular에게 컴포넌트 생성자에서 요구하는 HeroService를 제공하라고 알려준다. 

@Component에 있는 metatdata는 Angular에게  컴포넌트를 위한 주요한 빌딩 블록이 어디에 위치해있는지를 알려준다.

view를 묘사하기위한 template, metadata, component가 그것이다.

다른 metadata decorator로 Angular의 동작에 가이드를 하기 위한 역할을 한다. @Injectable, @Input, @Ouput이 인기있는 데코레이터이다. 

Data binding

프레임워크 없이 우리는 응답으로부터 데이터를 HTML 컨트롤에 데이터값을 전달하고 유저의 액션이나 값 변경에도 응답할 수 있다. 이러한 작업은 하기싫고, 에러에 취약하다. JQuery를 경험한 프로그래머들을 알고 있을 것이다.

Angular는 data binding을 지원한다. component의 template의 일부로 지원된다. HTML에 템플릿에 binding 마크업을 추가함으로써 가능하다.

Data Binding






위의 다이어그램에 나타났듯이 네가지 형태의 데이터 바인딩 syntax가 있다. 

HeroListComponent의 예제에서는 세가지 형태의 데이터 바인딩을 사용한다.

<li>{{hero.name}}</li>
<hero-detail [hero]="selectedHero"></hero-detail>
<li (click)="selectHero(hero)"></li>


HeroListComponent의 예제에서는 세가지 형태의 데이터 바인딩을 사용한다.

  • {{hero.name}} interpolation은 컴포넌트의 hero.name 프로퍼티 값을 출력한다.

  • [hero] property binding은 부모인 HeroListComponent의 selectHero결과를 자식인 HeroDetailComponent에 hero프로퍼티에 전달한다.

  • (click) event binding은 selectHero메소드를 호출하고 user가 클릭한 hero의 이름을 전달한다.

양방향 데이터 바인딩은 매우 중요한 네번째인데 프로퍼티와 이벤트 바인딩의 조합이다. ngModel 지시어를 사용한다. 여기 HeroDetailComponent template의 예제이다.

<input [(ngModel)]="hero.name">


양방향 데이터 바인딩은 데이터 프로퍼티의 값이 input box로 전달되다. 또한 user의 변경이 component로 전달된다. 

Angular는 모든 데이터의 한번의 JavaScript 이벤트 사이클을 통해 모든 데이터를 바인딩한다. 어플리케이션 컴포넌트 트리의 루트로부터 모든 자식 컴포넌트들로 전달된다.

매우 중요한 네번째인데 프로퍼티와 이벤트 바인딩의 조합이다. ngModel 지시어를 사용한다. 여기 HeroDetailComponent template의 예제이다.

Data Binding

데이터바인딩은 컴포넌트와 템플릿간의 상호작용이다.




Parent/Child binding

데이터바인딩은 또한 부모와 자식 컴포넌트간의 중요한 커뮤니케이션 수단이다.




Directives

Angular의 템플릿들은 다이나믹하다. Angular가 그것들을 렌더링하면 주어진 directives의 지시에 따라 DOM이 변경된다.

Directive는 directive metadata를 가진 class이다. TypeScript에서 @Directive 데코레이터를 적용함으로써 클래스에 metadata를 적용할 수 있다

component는 directive와 template이다. @Component는 실제로 @Directive 데코레이터의 확장판이다.
Directive에는 두가지 종류가 있는데 structural과 attribute가 있다.

이것들은 element tag의 속성으로 나타나는데, 때때로 assignment나 binding 타겟이된다.
Structural directives는 DOM의 엘레먼트를 추가, 삭제, 변경하여 layout을 변경한다.
예제 템플릿은 두가지 빌트인 structural directives이다.
<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero"></hero-detail>

*ngFor는 Angular에게 hero 리스트의 각 hero로 li가 하나씩 나타난다는 것을 요구한다.
*ngIf는 선택된 hero가 HeroDetail component에 나타난다고 말한다.

Attribute 지시어는 존재하는 엘리먼트의 행동이나 표현을 변경한다. 템플릿에서 일반적인 HTML로 보인다.
ngModel 지시자는 위의 예제에서 양방향 데이터 바인딩을 표현하는 attribute 지시어이다. ngModel은 <input>의 행동을 변경하고 변경된 이벤트에 응답한다. 

Angular는 몇몇의 layout 구조에 따른 지시어를 더 가지고 있다. ngSwitch 또는 DOM이나 components 외형에 관한 ngSytle, ngClass들이 그러하다.

물론 자기 자신만의 directives도 작성할 수 있다. HeroListComponet와 같은 컴포넌트도 하나의 custom directive이다.

Services

Service는 어플리케이션이 필요한 값, 함수등을 결합한 것을 의미한다. 
부분의 것들은 서비스 대상이 될 수 있다. 서비스는 전통적으로 잘 정의된 목적을 가진 클래스이다. 특정한 일을 수행한다.
서비스를 위해 Angular에 명시해야 할것은 없다. Angular는 서비스의 정의를 가지고 있지 않다. 서비스의 기본 클래스도 없고, 서비스를 등록해야할 장소도 없다.
하지만 Angular 어플리케이션에서 서비스는 주요한 요소이다. Component는 서비스의 중요한 소비자이다.
다음은 브라우저 콘솔에 로그를 출력하는 서비스의 예이다.
export class Logger {
  log(msg: any)   { console.log(msg); }
  error(msg: any) { console.error(msg); }
  warn(msg: any)  { console.warn(msg); }
}

HeroSerivce는 히어로들을 가지고 와서 리턴을 하는 것이다. HeroService는 Logger 서비스와 또 다른 서버 통신을 처리하는 BackendService와 연관되어 있다.
export class HeroService {
  private heroes: Hero[] = [];

  constructor(
    private backend: BackendService,
    private logger: Logger) { }

  getHeroes() {
    this.backend.getAll(Hero).then( (heroes: Hero[]) => {
      this.logger.log(`Fetched ${heroes.length} heroes.`);
      this.heroes.push(...heroes); // fill cache
    });
    return this.heroes;
  }
}


서비스는 어디든지 있을 수 있다. 컴포넌트 클래스는 의존적이다. 그들은 서버로부터 데이터를 패치할 수 없고, 유저 입력을 검사할 수 없으며 콘솔에 로그를 찍지 않는다. 이러한 작업들을 서비스에 위임하자.

컴포넌트의 작업은 유저 경험을 활성화하거나 아닐수도 있다. 컴포넌트는 view와 어플리케이션 로직간의 중개자이다. 좋은 컴포넌트는 프로퍼티의 표현과 데이터 바인딩을 위해서만 사용된다. 나머지 모든 것들은 서비스에 위임하자.

Angular는 이러한 원칙을 강요하진 않는다. 그러나 그렇지 않으면 3000줄짜리 컴포넌트가 생길 것이다.

Dependency Injection

DI는 객체를 요구하는 클래스에 클래스의 객체를 전달하는 방법이다. Angular는 dependency injection을 통해 컴포넌트에 필요한 서비스들을 제공한다. 

Angular는 생성자의 파라미터로 필요로하는 적합한 타입의 서비스를 컴포넌트에 제공한다. 

constructor(private service: HeroService) { }


Angular는 컴포넌트를 생성할때, 먼저 컴포넌트가 필요로하는 서비스를 injector에 요청한다.

Injector는 서비스 객체의 컨테이너를 관리한다. 만약 요청된 서비스 인스턴스가 컨테이너에 없다면 injector는 하나를 생성하고 컨테이너에 추가한다. 모든 요청된 서비스들은 할당되고, 리턴된다. Angular는 컴포넌트의 생성자를 호출하여 이러한 서비스들을 인자로 전달한다. 이것이 DI이다.

간략하게 말하자면 HeroSerive를 제공하기 위해 Injector에 등록을 해야한다. Provider는 Service를 생성하고 리턴한다.

모듈이나 서비스에 providers를 등록할 수 있다.

일반적으로 root module에 provider를 등록하는데 이렇게 하면 모든 곳에서 같은 서비스 인스턴스를 사용할 수 있다.

providers: [
  BackendService,
  HeroService,
  Logger
],


만약 컴포넌트 레벨에 등록을 하였다면 각 새로운 컴포넌트가 생길때마다 새로운 서비스가 생성된다는 것을 의미한다. 

DI에 중요하게 기억할 사항은 다음과 같다.

  • DI는 Angular 프레임워크로 엮어서 모든곳에서 사용할 수 있도록 해준다.

  • injector는 서비스 인스턴스를 생성하고 컨테이너를 유지한다.

  • inejector는 provider에 의해 새로운 서비스 인스턴스를 생성할 수 있다.

  • provider는 서비스를 생성하는 방법이다.

출처 : https://angular.io/docs/ts/latest/guide/architecture.html


'Programming > Angular2' 카테고리의 다른 글

Angular2 - 유저 입력  (0) 2016.10.14
Angular2 - 데이터 출력하기  (0) 2016.10.14
Comments