Angular2


FE-Weekly-Pick에서는 최근에 팀내에서 진행했던 스터디 내용을 정리하는 의미에서, 4회에 걸쳐 자바스크립트 (프론트엔드) 프레임워크를 소개하는 시리즈를 연재할 예정입니다. 아래와 같은 목차로 진행되니 많은 관심 부탁드립니다.

  1. Cycle.js
  2. Angular 2
  3. Vue.js
  4. React

목차

  1. 소개
  2. 개발환경
  3. 아키텍쳐
  4. 테스트
  5. 성능
  6. 정리

소개

Angular는 확장된 HTML 문법을 기반으로 하는 웹 애플리케이션 프레임워크로 개발되었다. 데이터 바인딩, 템플릿 문법 등과 같은 편리한 기능을 제공하여 웹 애플리케이션의 유지 보수, 개발 속도를 크게 향상했으며, 수많은 개발자에게 큰 인기를 얻고 있다. 또 그들의 커뮤니티 또한 많이 발달해 있어, 발전이 기대되는 프레임워크(혹은 플랫폼)로 손꼽힌다.

Framework to Platform

이제 더는 "AngularJS"가 아니다. "Angular"다.

angular2-is-not-angularjs
(https://AngularJS.org)

"AngularJS 1"은 웹앱 개발 생산성에 중점을 두었지만, "Angular 2"부터는 생산성은 물론, 여러 플랫폼에 맞춤형 개발을 할 수 있도록 개발의 범위를 확장하는 것에도 중점을 두고 있다. develop-across-all-platforms
(https://angular.io)

실제로 ng-conf의 비디오 자료들을 통해 Angular2의 웹 개발, 테스트, 모바일-프로그레시브 웹 앱, 서버 렌더링, 네이티브 앱, 머티리얼 디자인, 써드파티 프로그램 등 Angular2의 플랫폼을 간접적으로나마 경험할 수 있다.

TypeScript

javascript-in-typescript
(TypeScript: Angular 2's Secret Weapon)

Angular2는 기존의 JavaScript가 아닌 TypeScript를 주 언어로 권장하고 있다. 처음 접해보는 TypeScript에 대해 부담감을 느낄 수 있지만, JavaScript를 잘 이해하고 있다면 TypeScript에 대한 학습은 어렵지 않을 것이다.

TypeScript는 JavaScript의 확장 언어로 모든 JavaScript 문법을 포용한다. 때문에 js에서 ts로 확장자만 변경해도 문제없이 동작한다. TypeScript의 확장된 언어적 기능으로 사용자는 편리한 코드 툴링 기능을 사용할 수 있고, 개발 생산성도 크게 향상될 것이다. TypeScript 공식 홈페이지에서 더 자세한 기능과 설명을 확인할 수 있다.


## 개발 환경

개발 프레임워크를 도입할 때 개발 환경을 갖추는 것부터 하나의 큰 장벽처럼 느껴진다. 빌드 툴, 빌드 설정, 정적분석기(Linter), 트랜스파일러(Transpiler), 테스트 환경, 배포, 디버깅 등 갖춰야 할 환경이 너무 많다. 실제로 Angular2(이하 Angular) 프로젝트를 시작할 때 가장 큰 허들이 무엇인가에 대한 설문조사에 '개발 환경 세팅'이 26%로 1위를 차지했다.

largest-barrier
(https://twitter.com/Brocco/status/713374344823640064)

손수 개발환경을 준비하고 단순한 "app works!" 문구라도 브라우저에 나타내 보려면 다음과 같은 과정을 진행해야 한다.

  1. NPM 패키지 설정과 디펜던시 - package.json 작성
  2. 타입스크립트

    1. tsconfig.json 작성
    2. typgins.json 작성
    3. tslint.json 작성
  3. 모듈러/번들러 설정 (SystemJS 또는 Wepback, ...)
  4. 패키지 설치
  5. 폴더 구조 생성, 루트 모듈, 루트 컴포넌트, main(entry) 스크립트 작성
  6. index.html 작성
  7. npm start
  8. + 테스트 환경 설정 (Karma, Jasmine, Protractor, ...)

그런데 위와 같은 반복되는 설정과 패키지 설치를 3~4번의 command만으로 쉽게 처리할 수 있다면 편하지 않을까? 바로 Angular CLI가 그 역할을 해주는 도구다.

angular-cli
(https://cli.angular.io/)

Angular-CLI는 Yeoman과 유사한 프로그램으로, 초기 개발 환경 스캐폴딩 기능을 제공하며, 이 외에도 컴포넌트 추가/제거, 테스트, 빌드, 추가 라이브러리 등록 등 거의 모든 기반을 함께 제공한다. 이런 기능으로 우리는 쉽고 빠르게 애플리케이션을 개발할 수 있다. 1~2분이면 기본적인 개발 환경을 모두 구성하고 실제로 "app works!"를 브라우저에 나타내는 마법을 부릴 수 있다.

브라우저 지원범위

IE9 이상의 넓은 브라우저 지원 범위를 가지고 있다.

Chrome Firefox Edge IE Safari iOS Android IE mobile
latest latest 14 11 10 10 Marshmallow (6.0) 11
13 10 9 9 Lollipop (5.0, 5.1)
9 8 8 KitKat (4.4)
7 7 Jellybean (4.1, 4.2, 4.3)

(https://angular.io/docs/ts/latest/guide/browser-support.html)


## 아키텍처

Angular 애플리케이션은 기본적으로 템플릿과 프로퍼티(데이터)/이벤트 바인딩으로 동작한다. 이런 템플릿과 바인딩, 애플리케이션 로직을 합쳐 화면의 부분 부분을 구성하는 컴포넌트(Component)라 부른다.

또 Angular는 NgModules라 부르는 모듈 시스템을 가지고 있다. 모든 Angular 애플리케이션은 최소 1개 이상의 모듈을 가지고 있으며, 관례상 루트 모듈은 'AppModule'이라 부르며, 이 루트 모듈로 Bootstrapping을 수행하여 애플리케이션을 런칭한다. 이런 모듈 시스템으로 애플리케이션을 기능, 도메인 등에 따라 조직화 하고 각각에 필요한 Angular의 컴포넌트, 서비스, 지시자(Directives) 등을 통합하여 관리한다. 때문에 중복 코드를 피하거나 모듈의 지연 로딩(Lazy loading)등의 이점을 살릴 수 있다.

모듈 시스템과 같이 이 글에서 자세히 설명하지 못한 내용은 가이드 문서를 통해 폭넓게 파악할 수 있으니 Angular 개발을 시작한다면 꼭 읽어보길 추천한다.

컴포넌트(Component)

architecture-overview
(https://angular.io/docs/ts/latest/guide/architecture.html)

Angular에서 가장 중요한 부분이 바로 컴포넌트다. 컴포넌트는 화면을 구성하기 위한 하나의 단위다. 기본적으로 템플릿 + 메타데이터 + (컴포넌트)클래스 조합으로 구성하고 애플리케이션 로직을 정의한다. 템플릿은 기존에 우리가 알고 있는 HTML이다. 여기에 Angular만의 템플릿 문법(Syntax)을 추가하여 데이터나 이벤트를 바인딩한다. 그리고 메타데이터를 통해 단순한 클래스를 템플릿과 함께 연결시키며 Angular에게 컴포넌트로 등록한다. Angular는 이 데이터를 가지고 컴파일도 하고 실제 객체도 만들면서 렌더링을 한다. 그리고 이런 컴포넌트들은 각각 자신의 생명주기(Lifecycle)를 가지고 있어서 Hook을 통해 더 정교하고 세밀한 동작을 정의할 수 있다.

// app.component.ts

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

@Component({
  // Metadata
  selector: "app-root",
  templateUrl: "./app.component.html", // Template
  styleUrls: ["./app.component.css"]
})
export class AppComponent {
  // Class
  title = "app works!";
}

앞서 잠깐 언급하였듯 컴포넌트는 전체 화면을 구성하는 하나의 단위인데, 이 단위는 개발자가 쪼개고 싶은 만큼 쪼갤 수 있다. 그리고 이렇게 쪼개진 컴포넌트들로 트리를 구성하여 전체적으로 하나의 모듈 또는 페이지를 만들어 내는 것이다.

component-tree
(https://angular.io/docs/ts/latest/guide/architecture.html)

트리로 구성된 컴포넌트들은 @Input@Output을 통해 서로 통신할 수 있다. Angular-University에서 제공하는 코드 샘플@Input@Output으로 컴포넌트가 통신하는 방식이 잘 나타나 있다.

데이터 바인딩(Data Binding)

Angular의 데이터 바인딩은 컴포넌트와 DOM 간의 데이터 통신방식을 의미하지만, 코드상으로는 컴포넌트의 클래스와 템플릿 간의 데이터 통신으로 생각해도 된다.

databinding
(https://angular.io/docs/ts/latest/guide/architecture.html)

다음 3가지 바인딩 방식을 나타내는 예시를 보자.

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

그리고 여기에는 자주 놓치기 쉬운 부분이 있는데, 바로 바인딩 문법이 문자열로 작성되었어도 Angular는 이를 코드로 인식하여 Evaluation 한다는 것이다.

[hero]="selectedhero"

(js) heroDetail.hero = "selectedHero"; (x, 문자열 그대로 동작하지 않는다.)
(js) heroDetail.hero = this.selectedHero; (o, 문자열을 Evaluation 하여 동작한다.)

다음은 마지막 양방향 데이터 바인딩(Two-way data binding)방식 이다.

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

양방향 데이터 바인딩은 프로퍼티와 이벤트를 동시에 바인딩하는 방식으로 ngModel directive와 [()]문법을 사용한다. 이 경우 hero.name이 바뀔 때 input.value가 바뀌고 input.value가 바뀔 때 hero.name도 바뀐다. 때문에 각각 따로 이벤트와 프로퍼티를 바인딩할 필요 없이 간단한 문법으로 처리할 수 있다.

위 4가지 바인딩 방식과 동작은 Angular의 튜토리얼 예제 코드를 직접 실행하여 확인할 수 있다.

이 외에도 더욱 자세한 설명이나, 바인딩 시 HTML 속성과 DOM 프로퍼티의 차이, 단순 출력과 프로퍼티 바인딩의 쓰임 등과 같은 내용은 Angular의 템플릿 바인딩 문법 페이지에서 확인할 수 있다.

지시자(Directives)

Angular는 DOM을 다루는 모든 방식을 지시자로 표현한다. 즉 컴포넌트도 지시자의 한 종류다. 다만 컴포넌트 자체적으로도 매우 중요한 개념이기에 지시자와 분리하여 생각한다. 넓은 의미의 지시자가 컴포넌트를 포함하며 실질적으로 우리가 말하는 좁은 의미의 지시자는 컴포넌트를 제외한 속성 지시자와 구조적 지시자를 의미한다.

속성 지시자(Attribute Directives)

속성 지시자는 DOM의 외형이나 동작을 변경한다. 이상적인 속성 지시자는 컴포넌트와 관계가 없으며 또한 상세 구현에 묶이지 않은 형태로 동작하며 대표적으로 Angular의 ngStyle, ngClass 지시자 등이 속성 지시자에 속한다. 그리고 기본적으로 Property Binding과 같은 방식으로 동작한다. 사실 바인딩 자체가 지시자에 속한다고 생각해도 된다.

<p [style.background]="'lime'">I am green with envy!</p>
<!-- 참고: style과 ngStyle을 같이 사용한 경우는 함께 적용된다. -->
<p [style.color]="'white'" [ngStyle]="{background: myColor}">
  Background Color: {{myColor}}
</p>

구조적 지시자(Structural Directives)

구조적 지시자는 컴포넌트나 DOM을 추가/삭제하여 어떻게 화면에 나타내는지를 표현한다. 즉 실제 DOM트리 자체에 직접 연관이 있다고 볼 수 있다. 대표적으로 Angular의 *ngFor, *ngIf 지시자 등이 구조적 지시자에 속한다.

<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero"></hero-detail>

*ngFor의 경우 컴포넌트의 heros 리스트에 있는 hero 개수 만큼 <li> DOM을 복제한다. *ngIf는 선택된 hero가 있는 경우에만 HeroDetailComponent를 렌더링한다.

서비스(Service)

service-dependency-injection
(https://angular.io/docs/ts/latest/guide/architecture.html)

서비스 객체는 어떤 값, 함수, 애플리케이션에 필요한 기능 등 넓은 범위에 사용한다. 사실 거의 모든 것들이 서비스 객체가 될 수 있다. 로거 클래스라던가, HTTP 모듈을 통해 API를 호출하는 클래스, 어떤 복잡한 계산 로직을 가지고 있는 클래스 등 매우 다양하여 특별히 정의할만한 어떤 특징은 없다.

그래서 서비스 객체는 Angular가 특별하게 정의하고 제공하는 그런 기본 요소가 아니다. 하지만 그렇다고 해서 우리가 서비스 객체 없이 Angular가 제공하는 기능만으로 애플리케이션의 모든 로직을 작성하고 구현하기는 어렵다. 서버로부터 데이터를 받아오거나 검증하거나 콘솔에 출력하는 일은 컴포넌트의 책임이 아니기 때문이다. 컴포넌트는 사용자와의 상호작용, 뷰와 애플리케이션 로직의 전달자 역할만 하기에, 좋은 컴포넌트는 데이터 바인딩을 위한 속성/메서드로만 표현한다. 그래서 컴포넌트와 관련 없는 작업을 '서비스'라는 이름으로 처리하며, 서비스 객체 없이는 좋은 컴포넌트를 구현할 수 없다.

그리고 이런 서비스 객체는 Angular의 DI(Dependency Injection)을 통해 컴포넌트와 상호작용한다.


## 테스트

Angular의 로직들을 테스트하기 위해서는 여러 가지 방식이 있는데, Angular를 기준으로 테스트하는 방식을 따져보면 모듈, 컴포넌트, 서비스, 파이프, 지시자, 비동기, 이벤트, 라우터, end-to-end 테스트 등 각각이 필요로 하는 테스트 방식이 매우 많다. 다행히 각 테스트의 원리나 기법들을 Angular의 테스트 가이드에서 잘 설명하고 있기에, 테스트를 작성할 때 꼭 참고하도록 하자.

테스트 환경

JS의 테스트는 여러 프레임워크와 라이브러리의 조합으로 수행할 수 있지만, Angular는 보통 Karma-Jasmine 조합으로 테스트를 수행한다. 본래 Karma는 Angular 테스트를 쉽게 하기 위해 만들어졌으며, 확장성과 사용성이 좋아 대부분의 다른 JS 테스트에도 널리 쓰인다.

그런데 아무리 테스트 도구들을 사용하기 쉽게 만들었어도 실제로 그 환경을 맞추기 위해서는 많은 노력과 노하우가 필요하다. 그래서 이런 테스트 환경을 직접 구성하면서 쩔쩔매기보단 Angular-CLI를 통해 테스트 환경을 구축하길 추천한다.

컴포넌트 테스트

Angular에서 굳이 꼭 가장 중요한 테스트를 꼽자면 아마 컴포넌트 테스트일 것이다. 컴포넌트 자체가 Angular에서 매우 중요한 개념인 만큼 컴포넌트 테스트도 매우 중요하다. 컴포넌트를 테스트할 수 있는 방식으로 고립(Isolated) 테스트, Shallow 테스트, 통합(Integration) 테스트 3가지가 있다.

고립 테스트

고립 테스트는 복잡한 로직이거나 렌더링과 관계없이 로직을 테스트할 때 수행한다.

Form이 있는 컴포넌트를 테스트하는 경우를 생각해보자. 컴포넌트를 굳이 렌더링하지 않아도 Form의 onSubmit등의 핸들러를 직접 호출하여, 값에 대한 유효성이나, 발행하는 Action을 검증할 수 있다. 즉 '사용자에 의해 입력이 되었고', '어떤 이벤트가 발생하여 상태 값이 변경 되었다' 같은 가정 하에 해당 메소드나 동작을 테스트하는 것에만 집중하며, 일반적인 함수나 메서드에 대한 단위 테스트와 유사하다.

Shallow 테스트

때때로 메서드나 함수가 DOM에 의존성이 있어서 실제 렌더링 없이 테스트를 수행하기에 어려운 경우가 있다. 그렇다고 해서 전체 애플리케이션을 렌더링하기에는 테스트에 대한 비용이 너무 커지기 때문에 보통은 해당 컴포넌트만 렌더링하는 Shallow 테스트를 수행한다. Shallow 테스트는 각 케이스에 해당되는 컴포넌트의 템플릿만을 렌더링하며, 자식 컴포넌트들의 템플릿은 렌더링하지 않는다. 그래서 여전히 테스트를 하나의 컴포넌트로 고립시킬 수 있다.

NO_ERRORS_SCHEMA라는 스키마를 컴파일러에게 전달하면, 컴파일러는 인식되지 않은 엘리먼트와 속성들을 무시한다. 때문에 더는 불필요한 컴포넌트나 지시자를 선언할 필요가 없어진다.

import { NO_ERRORS_SCHEMA }          from '@angular/core';
import { AppComponent }              from './app.component';
import { RouterLinkStubDirective } from '../testing';

//...

  beforeEach( async(() => {
    TestBed.configureTestingModule({
      declarations: [ AppComponent, RouterLinkStubDirective ],
      schemas:      [ NO_ERRORS_SCHEMA ]
    })

    .compileComponents()
    .then(() => {
      fixture = TestBed.createComponent(AppComponent);
      comp    = fixture.componentInstance;
    });
  }));

  it('....', () => {....});

단 Shallow 테스트를 할 때는 컴파일러가 에러를 알려주지 않기 때문에, 개발자가 애초에 잘못 작성한 오타나 잘못 사용하고 있는 컴포넌트, 지시자에 대한 문제 역시 알려주지 않는다는 점을 주의해야 한다.

통합 테스트

마지막으로 통합 테스트는 모듈을 기준으로 테스트한다. Shallow 테스트는 테스트에 필요로 하는 객체들을 제외한 나머지 의존성을 전부 흉내 내지만, 통합 테스트는 실제 의존성을 그대로 사용한다. 그래서 아무리 Shallow 테스트를 열심히 그리고 꼼꼼하게 작성하였을지라도 결국 실제로 필요로 하는 모든 컴포넌트, 서비스 등의 구현체가 유기적으로 동작하는 경우와는 다를 것이다. Shallow 테스트는 대상 컴포넌트가 설계한 대로 동작하는지 확인하는 데 중점을 둔다면, 통합 테스트는 데이터의 정확성을 검증한다.

보통은 TestBed.configureTestingModule에서 테스트하고자 하는 모듈을 imports를 통해 구성하고 테스트를 수행한다.

End-To-End 테스트

Angular의 e2e 테스트는 보통 Protractor를 이용한다. Protractor는 Angular를 위한 end-to-end(이하 e2e) 테스트 프레임워크이자 NodeJS 프로그램이다. SeleniumWebDriverIO(WebDriverJS)를 이용하여 e2e 테스트를 지원한다.

protractor
(http://www.protractortest.org/#/infrastructure)

애플리케이션을 브라우저, 모바일에 구동시켜며 실제 사용자와 동일한 입력과 액션을 시뮬레이션하여 전체 애플리케이션을 행위 중심으로 테스트할 수 있다. 또한 Jasmine과 Mocha를 지원하기 때문에 일관된 스타일의 테스트 코드를 작성할 수 있다.


## 성능

ng-conf 2016의 Keynote에 따르면 Angular2는 전작보다 최초 혹은 러닝타임 렌더링 성능이 약 5배 더 빨라졌다고 한다. 컴파일러부터 지연 로딩, 서버 렌더링, 웹 워커, 웹 컴포넌트 등 수 많은 기술의 적극적인 도입과 최적화의 결과라고 볼 수 있다.

그중에서도 전작의 Dirty checking이 아닌 다른 방식의 변경 감지가 성능 향상에 있어 가장 큰 영향을 준 게 아닐까 싶다.

변경 감지(Change detection)

변경 감지는 애플리케이션의 뷰(화면)에 있는 DOM 혹은 내부 데이터 모델의 변화가 생길 때 감지하는 것을 말한다.

change-detection
(http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html)

예를 들면, 삭제 버튼을 눌러서 항목을 삭제하거나, 추가 버튼으로 항목을 늘리는 경우 DOM의 변경이 발생한다. 반대로 내부 코드에 의해 제한시간이 지나 게시물이 삭제된 경우, 프레임워크의 데이터 모델 변경이 발생한다. 이런 경우는 둘다 뷰의 변경이 발생하며 이를 Rerendering 이라고 부른다. 그렇다면 렌더링에서 다시 그려야 할 대상이 무엇인지 어떻게 알 수 있을까? DOM 트리에 접근하는 비용은 항상 크기 때문에, 어디가 업데이트되어야 하는지 알아야 하고, 가능한 한 DOM 트리 접근 횟수를 줄여야 한다.

여러 프레임워크마다 각각 다른 방법으로 이 문제를 해결했지만, Angular는 몇 가지 이벤트에 집중한다.

뷰의 업데이트 시기

Angular는 뷰의 업데이트가 필요한지 어떻게 알수 있을까?

@Component({
  template: `
    <h1> </h1>
    <button (click)="changeName()">Change name</button>
  `
})
class MyApp {
  firstname: string = "Pascal";
  lastname: string = "Precht";

  changeName() {
    this.firstname = "Brad";
    this.lastname = "Green";
  }
}

버튼을 클릭하면 컴포넌트의 상태 값을 변경하는 핸들러가 실행되고, 컴포넌트의 상태 값이 바뀌는 순간이 뷰를 업데이트해야 하는 순간이다.

다른 경우도 있다.

@Component()
class ContactsApp implements OnInit{

  contacts:Contact[] = [];

  constructor(private http: Http) {}

  ngOnInit() {
    this.http.get('/contacts')
      .map(res => res.json())
      .subscribe(contacts => this.contacts = contacts);
  }
}

컴포넌트가 초기화될 때, HTTP 요청을 보내게 되는데, 요청에 대한 응답이 올 때마다 리스트는 업데이트된다. 이 시점에 애플리케이션의 상태가 바뀌어 뷰를 업데이트해야 한다.

결론적으로 애플리케이션 상태 변경은 다음과 같은 비동기적인 동작 때문에 발생한다.

  • DOM 이벤트
  • XHR
  • Timer

그래서 역으로 보면 어떤 비동기처리가 수행되었다면, 애플리케이션의 상태는 변경될 가능성이 논다는 것이다. 이 비동기의 시점이 바로 Angular에게 뷰를 업데이트해야 한다고 알려야 할 시점이다. 하지만 위의 코드에서 우리는 뷰를 업데이트시키는 코드를 실행한 적이 없다. 그렇다면 Angular에게 뷰를 업데이트하라고 알려주는 일은 누가 할까? 정답은 'Zone이 알려준다' 이다.

Zone은 Angular와 무관하게 JS 자체의 비동기적 API들을 몽키패치하여 Hooking하는 라이브러리이며, Angular는 이 Zone을 외부 의존성으로 적극 활용하고 있는 형태이다. Angular는 NgZone이라고 불리는 고유의 zone을 가지고 있으며, Angular의 코드 어딘가에는 ApplicationRef라고 불리는 것이 있는데, 이는 NgZonesonTurnDone 이벤트의 변경을 감지하고 있다. 두 이벤트 중 하나가 발생하면 tick()을 실행하는데 이 함수가 바로 변경 감지에서는 빼놓을 수 없는 요소이다.

그럼 Angular는 전체 컴포넌트들의 유기적인 변화는 어떻게 처리할까? 단순하다. Angular의 각 컴포넌트에는 변화를 감지하는 고유의 디텍터(Change Detector)가 있다. 이 부분이 변경 발생 시 각 컴포넌트에 독립적으로 변경을 감지할 수 있게 한다. 컴포넌트 트리 어딘가에서 비동기 동작이 수행되면 Zone은 자신에게 주어진 핸들러를 처리하고 Angular에게 변경을 알린다. Angular는 각 고유의 디텍터들을 통해 실제 바인딩을 확인하고 업데이트 한다. 트리에서 데이터는 항상 Top-Down 형태로 흐른다. 이런 단방향 데이터 흐름이 순환 구조보다 더 예측하기 쉽다.

change-detection-2
(http://pascalprecht.github.io/slides/angular-2-change-detection-explained/#/59)

최적화?

Angular의 컴포넌트 트리 자체가 단순한 구조이면서, 코드 자체도 VM 친화적인 코드를 만들어내기 때문에, 모든 컴포넌트의 이벤트마다 확인한다 해도 빠르게 처리할 수 있다. 예를 들면 이벤트가 발생한 경우 몇 ms사이에 수십만 개의 컴포넌트를 확인할 수도 있다.

위에서 각 컴포넌트마다 디텍터가 있다고 했지만 그렇다고 해서 어떤 정형화된 디텍터를 각 컴포넌트에서 개별적으로 다룬다는 것은 아니다. Angular는 각 컴포넌트의 구조에 맞게 런타임에 디텍터의 클래스를 정의하고 생성 생성하기 때문에 컴포넌트에 알맞은 디텍터를 생성하면서도 VM친화적인 최적화된 코드를 생산한다.

그래서 우리는 성능에 대해 깊게 생각하지 않아도 된다. Angular가 알아서 해준다.

Immutable & Observable

어떤 비동기 이벤트던 일단 발생한다면 애플리케이션의 상태가 변할 수 있다. 그래서 그때마다 컴포넌트의 변경 여부를 전부 다 확인해야 한다는 사실은 우리를 여전히 불편하게 만든다.

변경을 감지하는 시간을 간단하게 표현해보면 다음과 같다.

전체 변경 감지 시간 = 하나의 바인딩을 확인하는 시간 * 전체 바인딩 개수

하나의 바인딩을 확인하는 시간은 앞서 설명했던것 처럼 동적인 디텍터의 생성으로 최적화를 하였다. 그렇다면 이제 남은 것은 전체 바인딩 개수를 조절해야 한다는 것인데, 사실 애플리케이션의 절대적인 전체 바인딩 수를 줄이기는 어렵다. 대신 변경을 감지할 때, 전체 바인딩을 전부 확인하는 것이 아니라, 변경된 바인딩만 확인할 수 있도록 만들 수는 있다. ImmutableObservable을 통해 변경되었는지 아닌지를 알 수 있기에 Angular를 더 빠르게 만들 수 있다.

보통 이런 최적화는 Angular의 변경 감지 전략(Change Detection Strategy)OnPush로 설정하여 처리한다.

Immutable

Immutable 객체는 내부 속성값이 변경되는 것이 아닌, 온전히 새로운 객체를 만들어 내용을 다시 채우는 방법으로 값을 변경한다. 그래서 수정을 가해도 이전의 객체와는 다른 별개의 객체를 가리키게 된다. 이 말인 즉, Angular의 상태가 변경되면 상태를 관리하는 객체 자체가 별개의 객체로 생성되어 깊은 비교 없이 레퍼런스 비교만으로 상태 변화를 감지할 수 있다는 것이다.

change-detection-immutable
(http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html)

Angular는 변경 감지 과정에서 변경이 되지 않은 하위 트리 전체를 건너뛴다. 이렇게 수행 횟수를 전체에서 일부로 줄여 성능 향상에 도움을 줄 수 있다.

Observable

Observable은 Immutable과는 다른 방식으로 변경이 발생했다는 것을 보장한다. Immutable이 객체의 레퍼런스를 가지고 보장했다면 Observable은 변경점에서 이벤트를 발생시켜 반응하게 한다. 그런데 Observable과 OnPush전략을 함께 사용하면, Observable 객체의 레퍼런스가 변경되지 않기 때문에, 하위 트리들의 업데이트가 발생하지 않는다. 만약 변경이 발생한 컴포넌트만 업데이트가 필요하다면 문제 없지만, 하위 컴포넌트들의 변경도 함께 업데이트가 필요하다면 문제가 될 수 있다. 그래서 이런 경우는 디텍터의 markForCheck()라는 API를 통해 변경이 발생한 컴포넌트의 서브 트리까지 업데이트를 시켜줄 방법이 있다.

change-detection-observable
(http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html)

이렇게 변경 감지의 범위를 조절하여 성능 향상에 도움을 줄 수 있다.

Web components

개발자가 직접 HTML 엘리먼트를 만드는 기술을 말한다. 자세한 내용은 공식 홈페이지를 통해 확인할 수 있다. Angular2는 내부적으로 Web Components를 차용하여 Native Element를 이용하기 때문에 기존 JavaScript 컴포넌트를 이용할 때 보다 훨씬 빨라졌다.


## 정리 Angular는 많은 부분이 바뀌었다.

AngularJS에서는 할 수 없던 서버-렌더링도 가능해지고, 성능향상, 주요 언어 교체 등 단점이라고 여겨지던 모습들이 많이 사라진 모습으로 새로 돌아왔다. 오히려 비교 대상으로 여겨지는 React와는 다르게 템플릿 코드를 별도 파일로 분리해서 관리 할 수 있어 비 개발자와의 협업에서 장점이 된다.
또한, Angular2는 프론트엔드 프레임워크이기 때문에 React보다 비교적 자유도가 낮지만 숙련되지 않은 개발자들에게 전체적인 설계 부담을 줄여준다. 더불어 사용자에게 기본적인 성능 최적화를 보장하기 때문에 성능에 깊게 고민할 필요가 없다는 점도 장점이다. 하지만 단점으로 여겨지는 Angular2 자체 코드의 크기가 크다는 문제가 남았는데, 실제 사용하는 코드만 뽑아서 번들링하면 해소할 수 있다.

Reference


이민규, FE Development Lab2016.10.07Back to list