프레임워크를 적용하는 이유
- 웹 접근성 이슈
- 규모가 큰 애플리케이션에서 구조, 디자인 패턴, 코드 스타일 등
앵귤러 프레임워크
- 커스텀 컴포넌트
- 로직 구현이 간단
- 데이터 바인딩
- 의존성 주입 패턴 도입 가능
- 모듈화 지원
- 라우팅
- MVC 패턴과는 다르다.
버전
지금 앵귤러의 버전 관련해서 너무 헷갈린다. 앵귤러1이랑 앵귤러2가 있는데 버전4짜리는 뭐지?
사이트도 지금 Angularjs.org 랑 angular.io 이렇게 2개가 있는데 흠~~
angularjs.org 에서 배포하는 버전의 경우
- 1.6.x (latest)
- 1.2.x (legacy)
AngularJS 가 1버전 때를 말한다면 개선된 버전이 Angular 이다.
앵귤러 인기 요인
- HTML 태그나 어트리뷰트의 기능을 새롭게 만드는 directive 라는 개념을 제공하며, 애플리케이션의 요구 사항에 맞게 HTML 태그를 확장할 수 있다.
- 애플리케이션의 영역을 과도하게 침범하지 않는다. <div> 에 ng-app 어트리뷰트를 추가하면 그 <div> 만 영향을 받고, 그 외의 영역은 순수한 HTML과 JS로 동작한다.
- 뷰 영역에 데이터를 쉽게 연결할 수 있다. 데이터의 값이 변경되면 연결된 화면이 자동으로 갱신되며, 화면에서 값이 변경되면 연결된 데이터도 자동으로 변경된다.
- 자유롭게 설정할 수 있는 라우터를 제공한다. URL 패턴을 애플리케이션의 컴포넌트와 연결해서 특정 페이지가 원하는 컴포넌트로 동작하게 할 수 있다.
- 애플리케이션의 데이터 흐름은 프로퍼티와 함수를 정의해 둔 컨트롤러에 정의된다.
- 컨트롤러와 뷰에서 공유하는 데이터들은 scope 객체에 저장되며, 이 객체는 여러 계층으로 구성된다.
- 의존성 주입 패턴을 사용해서 애플리케이션의 코드 결합도를 낮춘다.
뷰와 모델이 서로를 갱신하는 양방향 바인딩
모델이 갱신될 때마다 전체 애플리케이션에 영향을 미치는 $digest 함수를 실행하면서 데이터 바인딩을 점검하고 DOM 객체의 값을 갱신
$digest 함수가 실행되기 때문에 애플리케이션 성능에 큰 영향을 줄 수 있다.
모델의 값은 $scope 라는 스코프 객체 안에 선언되며, 여러 계층으로 구성한다.
$rootScope : 애플리케이션 전체 범위에 사용되는 스코프
컨트롤러와 디렉티브는 독자적인 $scope 객체를 갖는다.
module 객체를 사용하면 모듈을 만들거나 불러와서 사용할 수 있다.
모듈은 컨트롤러나 다른 모듈, 서비스와 같은 다른 객체의 영향을 받으며, 의존성 주입 방식에 의해 모듈 인스턴스가 생성되거나 주입된다.
// 모듈에 다른 객체가 어떻게 주입되는지 보여주는 코드
var SearchController = function ($scope) {
// ...
};
SearchController['$inject'] = ['$scope'];
angular.module('auction').controller('SearchController', SearchController);
- SearchController를 선언하면서 $scope 항목을 인자로 받는다
- 인자로 전달된 $scope 객체를 주입하기 위해 컨트롤러에 $inject 프로퍼티를 추가한다
- auction 모듈에 SearchController 객체를 컨트롤러로 지정한다
// 일반적인 Angular 애플리케이션의 index.html
<!DOCTYPE html>
<html>
<head>
<title>ANgular seed project</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/typescript/lib/typescript.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="node_modules/rxjs/bundles/Rx.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function (err) { console.error(err); });
</script>
</head>
<body>
<app>Loading...</app>
</body>
</html>
// Controller 예제
@Component({
selector : 'search-product',
template :
`
<form>
<div>
<input id="prodToFind" #prod>
<button (click)="findProduct(prod.value)">Find Product</button>
Product name : {{ product.name }}
</div>
</form>
`
})
class SearchComponent {
@Input() productID : number;
product : Product; // Product 클라스의 내용은 생략
findProduct(prodName : string) { // 클릭 핸들러 함수는 여기에 구현 }
// 더 필요한 코드는 여기에 구현
}
product 객체를 클래스 멤버 변수로 선언하고, Product 클래스의 프로퍼티 중 하나인 name을 화면에 표시한다.
템플릿 지역 변수인 #prod 는 변수가 선언된 엘리먼트를 가리키기 때문에, 이 엘리먼트의 값을 얻기 위해 추가로 DOM을 쿼리할 필요는 없다.
(click) 구문은 클릭 이벤트를 뜻한다. 이때 이벤트 핸들러 함수는 부모 컴포넌트에서 @Input() 어노테이션으로 바인딩되어 인자로 전달된 productID 를 받아 동작한다.
MVC 기반 프레임워크였던 AngularJS 와는 다르게 Angular 는 컴포넌트 기반 프레임워크이다.
그렇기 때문에 컨트롤러를 따로 만들 필요가 없다. 컴포넌트와 컴포넌트에 주입된 서비스는 각각 필요한 코드를 클래스로 구현함
TypeScript 코드와 HTML을 명확하게 분리하기 위해 @Component 어노테이션의 template 항목은 별개의 파일로 나누고 url로 가능.
AngularJS 에서는 모든 디렉티브가 전역 스코프에 로드되었지만, Angular 에서는 필요한 요소만 모듈 범위에서 로드하는 방식을 사용해서 좀 더 나은 캡슐화를 제공한다.
의존성 주입
컴포넌트 생성자를 통해서만 의존성 지정
@Component({
selector : 'search-product',
providers : [ProductService],
template : `<div>...</div>`
})
class SearchController {
products : Array<Product> = [];
constructor (productService : ProductService) {
this.products = productService.getProducts();
}
}
Angular 는 다음과 같은 점에서 AngularJS 보다 단순하다.
- 애플리케이션을 구성하는 각 컴포넌트는 뷰, 컨트롤러, 변화 감지 메커니즘의 관점에서 보면 온전히 캡슐화되어 있다.
- 컴포넌트 클래스는 어노테이션을 지정해서 원하는 용도로 바꿔서 사용할 수 있다.
- 스코프 계층에 대해 신경 쓸 필요가 없다.
- 의존성이 있는 컴포넌트는 컴포넌트 생성자를 통해서만 주입된다.
- 데이터 바인딩은 기본적으로 단방향 바인딩이며, 필요한 경우에만 양방향 바인딩을 사용한다.
- 변화 감지 메커니즘이 새로워졌고, 이전보다 빠르다.
성능향상
프레임워크의 렌더링 성능을 비교하는 Repaint Rate Chanllenge (http://mathieuancelin.github.io/js-repaint-perfs 라는 사이트 에서 AngularJS와 Angular의 성능을 비교해보면, 얼마나 향상되었는지 확인가능.
툴
- ES6 : 최신기능 자바스크립트
- TypeScript : 자바스크립트에 타입을 도입
- Babel : 작성된 코드를 낮은 버전에서도 돌아가게 트랜스파일
- SystemJS : 모듈을 불러오는 모듈 로더
- AngularCLI : 코드 생성 자동화 툴
- Node.js : JS 엔진으로 만들어진 플랫폼
- npm : 패키지 매니저
- Grunt : 개발이나 배포에 필요한 수많은 단계를 자동화할 수 있는 태스크 실행기
- Gulp : Grunt 같은거인데 Grunt 가 JSON 파일을 사용하는 반면 Gulp는 JS 코드를 사용해서 작업을 정의
- JSLint, ESLint : JS 코드나 JSON 문서 검사.
- Webpack : 모듈 번들러. 여러 개로 나뉜 파일과 의존 관계에 있는 라이브러리들을 하나의 파일로 묶어준다. 결과적으로 서버에 요청하는 횟수를 줄여서 프론트엔드에서 내려받아야 하는 코드 용량을 줄여준다.
- Jasmine, Karma : 테스트 프레임워크와 테스트 러너
- 반응형 웹 디자인을 지원하기 위해 Bootstrap UI 라이브러리 또는 Google 의 Material UI
모든 툴에 대해서 이해하고 사용할 필요는 없지만 다음은 중요
- npm 스크립트를 사용해서 웹 서버를 실행하거나 빌드 자동화 등의 태스크를 실행하기도 한다.
- 유틸리티를 실행하거나 서버에서 동작하는 프레임워크를 실행할 때는 Node.js 환경을 사용
- 브라우저에 TypeScript 를 불러올 때는 SystemJS 를 사용한다.
- 애플리케이션 코드를 배포할 때 코드를 압축하고 번들링 하는 작업은 Webpack 을 사용
Angular 기능 구현 방식
| 작업 | 구현 방식 |
|---|---|
| 업무 로직 구현 | 클래스를 사용한다. 클래스가 의존성으로 주입되는 경우에는 Angular 프레임워크에서 인스턴스를 생성해서 해당 컴포넌트에 주입한다. |
| UI 컴포넌트 구현 | 클래스에 @Component 어노테이션을 붙여서 생성 |
| 컴포넌트에 렌더링 될 HTML 템플릿 정의 | 인라인 HTML 코드를 사용할 때는 @Component 어노테이션에 template 항목을 사용하고, 외부 파일에서 불러올 때는 TemplateUrl 항목을 사용한다. |
| HTML 조작 | *ngIf 나 *ngFor 와 같은 HTML 조작 디렉티브를 사용하거나 @Directive 를 사용해서 별개의 클래스를 만든다. |
| 현재 객체에서 클래스 변수 참조 | this 키워드를 사용한다. |
| 단일 페이지 앱에서 내비게이션 구현 | 라우터를 사용해서 컴포넌트와 URL 을 연결하고 컴포넌트의 템플릿이 렌더링될 위치를 <router-outlet> 태그로 지정한다. |
| 컴포넌트 프로퍼티를 UI 에 표시 | 템플릿의 이중 중괄호 안에 클래스 프로퍼티를 사용한다. |
| 컴포넌트 프로퍼티를 UI 와 바인딩 | 대괄호를 사용해서 프로퍼티를 바인딩 |
| UI 이벤트 처리 | 이벤트 이름을 괄호로 감싸고 핸들러를 지정한다. |
| 양방향 바인딩 | [( )] 표기를 사용한다. |
| 컴포넌트에 데이터 전달하기 | 컴포넌트 프로퍼티를 선언할 때 @Input 어노테이션을 사용해서 외부 값과 연결한다. |
| 컴포넌트에서 데이터 받기 | 컴포넌트 프로퍼티를 선언할 때 @Output 어노테이션을 사용하고 EventEmitter 를 이용해서 이벤트를 발생시킨다. |
| HTTP 요청 | 컴포넌트에 Http 객체를 주입하고 HTTP 함수를 사용한다. |
| HTTP 응답 처리 | subscribe() 함수를 사용해서 옵저버블 스트림을 처리한다. |
| HTML 일부를 자식 컴포넌트에 전달하기 | 자식 템플릿에 <ng-content> 태그를 사용한다. |
| 컴포넌트 상태가 변경되는 것을 가로채기 | 컴포넌트 생명주기 함수를 사용한다. |
| 배포 | 번들러로 애플리케이션 파일과 기타 등등을 번들링 |