Page tree
Skip to end of metadata
Go to start of metadata

CHAPTER04 뷰를 구성하는 기초

 - homepage 를 해석해 보면 애플리케이션 실행시 컴파일러를 사용하여
   템플릿(html) 파일에서 앵글러 고유의 추가적인 문법을 통하여 렌더링 할 정보와 이벤트 처리 로직등을 바탕으로 DOM을 만드는데 이를 뷰라고 함.
 - 애플리케이션 실행 시 앵귤러는 컴포넌트와 템플릿 코드를 바탕으로 뷰를 생성
 - 앵귤러는 프레임워크로서 컴포넌트가 제공하는 정보를 뷰에 전달하고, 뷰에 일어나는 사용자 액션을 처리

angular가 뷰를 생성하는 과정
a. HTML로 작성한 템플릿을 브라우저가 읽음
b. 브라우저는 DOM 생성
c. <script src="angular.js">가 실행되어 AngularJS 소스 실행
d. DOM 생성 시 DOM Content Loaded 이벤트가 발생하면서 정적 DOM을 읽고 뷰를 컴파일, 
   컴파일 시 확장 태그나 속성을 읽고 처리한 후 데이터 바인딩
e. 컴파일 완료되면 동적 DOM, 뷰를 생성

4.1 컴포넌트

4.1.1 컴포넌트의 선언

 - 화면을 구성하는 하나의 단위. Template + Class(Properties, Method) + Metadata
 - 클래스에 @Component 데코레이터를 사용
 - @angular/core 패키지에서 import.

4.1.2 메터데이터

 - @Component 데코레이터를 이용하여 설정 정보, 메터 정보를 받음.
 - 메터데이터 종류는 총 18가지. 템플릿 정보와 selector는 사실상 필수.

 

 Click here to expand...
  • animations - list of animations of this component
  • changeDetection - change detection strategy used by this component
  • encapsulation - style encapsulation strategy used by this component
  • entryComponents - list of components that are dynamically inserted into the view of this component
  • exportAs - name under which the component instance is exported in a template
  • host - map of class property to host element bindings for events, properties and attributes
  • inputs - list of class property names to data-bind as component inputs
  • interpolation - custom interpolation markers used in this component's template
  • moduleId - ES/CommonJS module id of the file in which this component is defined
  • outputs - list of class property names that expose output events that others can subscribe to
  • providers - list of providers available to this component and its children
  • queries - configure queries that can be injected into the component
  • selector - css selector that identifies this component in a template
  • styleUrls - list of urls to stylesheets to be applied to this component's view
  • styles - inline-defined styles to be applied to this component's view
  • template - inline-defined template for the view
  • templateUrl - url to an external file containing a template for the view
  • viewProviders - list of providers available to this component and its view children

 

1) 템플릿 정보

 a. template
  - 템플릿 코드를 메터 데이터 안에 직접 기술

@Component({
	selector : 'af-simple',
	template : `<h1>Template Test</h1>`
})

 b. templateURL
  - html 파일로 분리

@Component({
	selector : 'af-simple',
	templateUrl : './welcome-msg.component.html',
	styleUrls : ['./welcome-msg.component.css']
}) 

2) selector

 - 요소명을 정의하기 위해 사용
   예를 들어 컴포넌트 메터데이터에 selector : 'my-test' 와 같이 작성하였다면 다른 컴포넌트의 템플릿 안에서 <my-test></my-test>와 같이 사용함.

3) 기타정보

 - styles, stylesUrl같은 스타일 속성이 있음
 - stylesUrl 은 css 외부 파일로 배열안에 복수 형태.

4.1.3 부트스트래핑

 - 브라우저에서 애플리케이션을 최초 실행할 때 진행되는 과정

app.modules.ts 중에서
@NgModule({
	declarations: [
	AppComponent
	],
	imports: [
	BrowserModule,
	FormsModule,
	HttpModule
	],
	providers: [],
	bootstrap: [AppComponent]
})
export class AppModule { }

 - NgModule 데코레이터가 메타데이터를 받고 있음
 - bootstrap은 애플리케이션을 부트스트래핑할 때 어떤 컴포넌트를 사용할 것인지 배열로 선언한 컴포넌트 정보
   bootstrap에 선언된 컴포넌트는 브라우저에서 최초 index.html을 요청하여 애플리케이션을 실행할 때 사용할 컴포넌트
 - declarations 는 앵귤러 애플리케이션에서 사용할 모든 컴포넌트를 배열로 선언
 - 앵귤러는 우리가 작성한 컴포넌트, 지시자 등의 코드를 해석하여 일반적인 자바스크립트 코드로 변환 하는데 이를 컴파일 이라 함. 컴파일은 추가적인 설정이 없으면 브라우저에서 애플리케이션이 실행될 때 이루어 짐
 - bootstrap 을 실행하는 위치는 앵귤러 CLI로 생성한 프로젝트의 경우 src/main.ts에 있음

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
	enableProdMode(); // 운영 환경만 적용. 301page 참조
}
platformBrowserDynamic().bootstrapModule(AppModule);

   platformBrowserDynamic 메서드는 앵귤러 애플리케이션이 실행될 플랫폼을 브라우저로 지정하는 목적과 함께
   애플리케이션의 부트스트랩을 위한 PlatformRef 타입의 객체를 반환.
   PlatformRef의 역할은 애플리케이션이 실행될 플랫폼에 맞게 앵귤러 모듈을 부트스크랩함.
   bootstrapModule은 최초 실행할 하나의 모듈을 인자로 받음, 모든 애플리케이션에서 사용할 모듈의 베이스가 되는 루트모듈.
 - 타입스크립트로 작성한 코드는 앵귤러 CLI로 빌드하는 과정에서 이미 자바스크립트로 변환됨.
   bootstrap에서의 컴파일은 컴포넌트의 템플릿을 자바스크립트 코드로 변환하는 것을 말함.
   더 정확하게 말하면 JIT(Just In Time) 컴파일이라 부름.
   브라우저에서 컴파일하는 과정을 표현하는 용어, 메모리 상에만 존재.
 - 최초 애플리케이션 실행까지의 지연이 발생하며 compile을 위한 번들링된 결과물 @angular/compiler: "^4.0.0" 크기가 큼
 - JIT 컴파일 단점을 극복하기 위해 AOT(Ahead Of Time) 등의 방법 등이 있으나 플랫폼 종속적인 코드가 없어야 함

4.1.4 컴포넌트 트리

 - 컴포넌트가 여러 컴포넌트를 사용하여 뷰를 구성하기 때문에 반드시 컴포넌트 트리가 존재
 - index.html에서 사용할 컴포넌트가 트리의 루트가 됨

      

 - 뷰와 컴포넌트 일대일 관계

4.2 템플릿

 - 컴포넌트의 뷰를 구성하는 정보. 컴포넌트가 템플릿을 기반으로 뷰에 데이터를 전달하고 사용자의 이벤트에 적절한 로직을 수행.

4.2.1 절차적 방식과 선언적 방식

 1) 절차적 프로그래밍

  - 물이 위에서 아래로 흐르는 것처럼 순차적인 처리가 중시되며 프로그램 전체가 유기적으로 연결되도록 만든 프로그래밍 기법
    웹을 예를 든다면 DOM에 직접 접근하여 데이터를 노출 시키거나 뷰의 상태를 가져와 일련의 로직을 처리하는 등
    일일이 수행하여야 할 명령을 기술하는 방식

 2) 선언적 프로그래밍

  - 해법을 정의하기 보다는 문제를 설명하는 고급언어, 무엇을 할것인지에 중점을 둠.
  - 다른 정의에 따르면, 프로그램이 함수형 프로그래밍 언어, 논리형 프로그래밍 언어, 혹은 제한형 프로그래밍 언어로 쓰인 경우에 "선언형"이라고 한다.
    여기서 "선언형 언어"라는 것은 명령형 언어와 대비되는 이런 프로그래밍 언어들을 통칭함
  - 프로그래머가 어떤 방법으로 답을 계산해야 하는지를 정의하지 않고 관계를 정하거나 서로의 관계를 묻는 질문을 하기 때문에 선언형이다.
    함수형 프로그래밍 언어는 어떤 연산도 정해진 순서로 계산되어야 한다는 것이 정의되지 않고 함수들의 입력과 출력이 서로 연결되어 있기 때문에 선언형이다.
    웹에서 컴포넌트와 뷰 사이에 연결 고리를 만드는 방식은 뷰를 구성하는 템플릿 안에 컴포넌트와 뷰의 관계를 선언

 a. 절차적 방식

var check1 = $("#comfirm-checkbox1").prop("checked");
var check2 = $("#comfirm-checkbox1").prop("checked");
if(check1 && check2) 로직 수행

 b. 선언적 방식

@Component({
... 생략 ...
<input type="checkbox" id="confirm-checkbox1" [(ngMode)]="isConfirm1" />
<input type="checkbox" id="confirm-checkbox2" [(ngMode)]="isConfirm2" />
...
export class AppComponent {
	isConfirm1: boolean,
	isConfirm2: boolean,
	...
	if(isConfirm1 && isConfirm2) 로직 수행
	... 
	}

 - 절차적 방식의 코드 처럼 필요한 정보를 얻기 위한 과정이 없음. binding 을 통해 얻음
 - 뷰의 데이터 및 이벤트와 컴포넌트의 특정 속성 및 메서드의 연결 고리를 선언하는 일.

4.2.2 데이터 바인딩

 - 앵귤러로 선언적 프로그래밍을 하는 근간.
 - 컴포넌트와 뷰 사이에 연결 고리를 맺는 구체적인 방법. 바인딩

 1) 단방향 바인딩

  a. 삽입식
   - 문자열로 변환될 수 있는 값을 뷰에 바인딩
   - 이중 중괄호 {{}} 안에 최종적으로 문자열 값으로 변환되는 내용을 넣음

<div>{{ contents }}</div>
<div>{{ getContents() }}</div>
<div>{{ someLink + '?who=angular' }}</div>
<div>{{ 1 + myVal }}</div> ..... 속성을 더한후 문자열 치환

   - 템플릿 표현식의 유효 컨텍스트는 컴포넌트다.(컴포넌트의 속성, 메서드만 가능)
     Side Effect를 일으키는 복잡한 수식(시간이 오래 걸리는)을 넣지 않음
   - 복잡한 연산이나 로직은 컴포넌트에서 작성, 뷰에 노출할 데이터 가공이 필요한 경우 파이이 이용

  b. 프로퍼티 바인딩
   - 왼쪽에 DOM의 프로퍼티를 대괄호로 감싼 후 오른쪽에 삽입식과 마찬가지로 템플릿 표현식을 작성

<p [textContext]="contents"></p>
<img [src]='/some/image.png' [width]='10*20' />
<img [src]='imageUrl' width='123' /> ..... 3번
<img src='{{imageUrl}}' width='123' /> ..... 4번(3번과 동일한 결과)
<p [style.color]="isReally ? 'blue':'grey'">Hello Angular</p>

   - 문자열이 아닌경우 프로퍼티 바인딩 사용
   - 단일 클래스나 스타일이 아닌 경우는 프로퍼티 바인딩보다 NgClass나 NgStyle을 사용

  c. 이벤트 바인딩
   - 왼쪽에 괄호로 이벤트 이름을 감싼 후 오른쪽에 이벤트 발생 시 실행할 템플릿 문장을 작성

<button type="button" (click)="confirm()">확인</button>
<div (mousemove)="printPosition($event)"></div>
<input type="text" (keyup)="myStr = $event.target.value" />
<button type="button" (click)="clicked = true; callEvent($event)">확인</button>

   - $event는 이벤트 발생 시 전달하는 표준 이벤트 객체

 2) 양방향 바인딩

  - 뷰와 컴포넌트의 상태 변화를 상호 간에 반영해 줌
  - 엄밀히 말하면 양방향 바인딩은 존재하지 않음, 프로퍼티 바인딩과 이벤트 바인딩만 있을 뿐
  - 가장 기본적인 출발은 FormsModule에서 제공하는 NgModel 지시자를 사용.

<input type="text" [(ngModel)]="myData" /> 
아래와 같이 앵귤러가 이해하고 프로퍼티 바인딩과 이벤트 바인딩으로 나워서 처리
<input type="text" [ngModel]="myData" (ngModelChange)="myData = $event" />

  - angular에서는 기능별로 컴포넌트를 나누고 셀렉터를 이용해서 다른 컴포넌트에서 해당 컴포넌트를 호출한다. 
    호출한 컴포넌트와 호출된 컴포넌트간에 서로 데이터를 주고받는 것이 필요한데, 이 때 이용하는 것이 @Input @output 이다.

  - B컴포넌트에서 selector를 이용해 A컴포넌트를 호출했다고 하자. 
    B컴포넌트의 데이터를 A컴포넌트가 읽어올 수 있도록 할 때 @Input 을 이용한다.
    반대로 A컴포넌트의 작업 결과를 자신을 호출한 B 컴포넌트에게 전달하려고 할 때 @Output 을 이용한다.

 

 3) 실습: 계수기(예제 4-11, 4-12 참조)

  - 숫자를 표시하는 화면, +,- 버튼, 수동으로 값 조정, 음수 빨강, 양수 녹색

 

<div class="counter" [style.backgroundColor]="colorByValue()">{{curVal}}</div>
<div class="row buttons">
	<button type="button" (click)="inc()">+</button>
	<button type="button" (click)="dec()">-</button>
</div>
<div class="row manual-action">
	<label for="manual-val">수동 수정:</label>
	<input type="number" id="manual-val" [(ngModel)]="manualVal">
	<button type="button" (click)="setValueForcibly()">강제 저장</button>
</div>
.......................................................
import { Component } from '@angular/core';
@Component({
	selector: 'app-root',
	templateUrl: './app.component.html',
	styleUrls: ['./app.component.css']
})
export class AppComponent {
	curVal = 0;
	manualVal = 0;
	static LIMIT_CNT = 100;
	
	colorByValue() {
		if (this.curVal > 0) return 'green';
		else if (this.curVal < 0) return 'red';
		else return 'grey';
	}
	inc() {
		const tempVal = this.curVal + 1;
		if (this.checkLimitCnt(tempVal)) {
			this.curVal = tempVal;
		}
	}
	dec() {
		const tempVal = this.curVal - 1;
		if (this.checkLimitCnt(tempVal)) {
			this.curVal = tempVal;
		}
	}
	setValueForcibly() {
		if (this.checkLimitCnt(this.manualVal)) {
			this.curVal = this.manualVal;
		}
		this.manualVal = 0;
	}
	checkLimitCnt(val) {
		if (Math.abs(val) < AppComponent.LIMIT_CNT) {
			return true;
		}
		const target = val > 0 ? '증가' : '감소';
		alert(`더 이상 ${target}시킬 수 없습니다.`);
		return false;
	}
}

 

4.2.3 지시자

 - DOM을 다루기 위한 모든 것
 - Component도 지시자. Directive 인터페이스를 상속 받은 지시자 속성에 템플릿, 스타일 정보 등 뷰를 그리는데 필요한 메터데이터를 추가.
 - 좁은 의미로는 뷰를 가지지 않지만 DOM의 표현과 동작을 풍성하게 만드는 요소로 활용

 1) 구조 지시자

  - DOM 요소를 추가하거나 삭제하는 등 DOM 트리를 동적으로 조작하여 화면의 구조를 변경할때 사용
 a. NgIf
  - *ngIf를 선언 후 boolean 값을 주면 됨

<button type="button" (click)="isShow = !isShow">전화</button>
<label *ngIf="isShow; else hiding">You are special</label> .... 1
<ng-template #hiding>
<label>test</label> .......... 2
</ng-template> .......angular2에서 template이 deprecated 됨. angular4에서 ng-template 
true 이면 1번이 false 이면 2번이 표시됨
<div *ngIf="member">
<p>name : {{member.name}}</p>
</div>

member가 null 혹은 undefined 라면 전체가 비표시

 b. NgFor

  - 배열 형태의 모델을 DOM에 반복 표현
  - 인덱스가 필요한 경우 let (변수명)=index와 같이 앵귤러에서 제공하는 index 변수를 추가로 선언

<ul>
<li *ngFor="let animal of animals; let idx = index">{{ idx + 1 }} {{animal}}</li>
</ul>

 c. NgSwitch

  - [ngSwitch]에 바인딩할 속성을 선언하고 자식 요소의 속성에는 *ngSwitchCase에 각 케이스 값을 바인딩

<span [ngSwitch]="animal">
<span *ngSwitchCase = "'Dog'">멍멍</span>
<span *ngSwitchCase = "'Cat'">야옹</span>
<span *ngSwitchDefault>에험</span>
</span>

   ngSwitch를 사용하기 위해 span 태그가 더미로 사용됨, 이와 같은 경우 ng-container를 사용하여 DOM 트리에 포함시키지 않고 그 안의 내용만 뷰에 포함

<span [ngSwitch]="animal">
<ng-container *ngSwitchCase = "'Dog'">멍멍</ng-container>
<ng-container *ngSwitchCase = "'Cat'">야옹</ng-container>
<ng-container *ngSwitchDefault>에험</ng-container>
</span>

 

 2) 속성 지시자

- 지시자가 선언된 DOM의 모습이나 동작을 조작하는데 사용됨. NgClass, NgStyle가 있음

<div [class.my-class]="isMyClass">프로퍼티 바인딩 예시</div>
<div [ngClass]="myObj">NgClass 속성 지시자</div>
myObj = {'test-class': true, 'your-class': 0, test: true}
값이 true 인 class만 반영, test-class, test만 반영됨

<p [ngStyle]="styleConf">ngStyle 속성 지시자</p>
this.styleConf = { color: this.boilable? 'green' : 'yellow', 'font-weight': this.edible? 'bold' : 'normal' };

 

4.2.4 파이프

 - 뷰에 노출할 데이터를 변환 하거나 가공할때 사용
 - 기본 파이프로 DatePipe, UpperCasePipe, LowerCasePipe, CurrencyPipe, PercentPipe등이 있음

<p name="member-name">{{ myName | uppercase }}</p>


 1) 커스텀 파이프

  - 이름에 걸맞게 여러 파이프를 붙여서 사용

import { Pipe, PipeTransForm } from '@angular/core';
@Pipe({name : 'honor'})
export class HonorPipe implements PipeTransform {
	transform(value: String) {
		return '${value}님';
	}
}

  - name을 받고, PipeTransform 인터페이스를 구현

<p name="member-name">{{ myName | uppercase | honor }}</p>
value 인자뒤에 가변인자로 args 배열을 받음

transform(value: String, level: String) {
	if(level === 'A') return '${value} Man';
	else if(level === 'B') return '${value} Woman';
	else return '${value}';
}
<p name="member-name">{{ myName | uppercase | honor | test:'A' }}</p>

- 파이프 이름뒤에 콜론을 구분자로 두고 인자를 전달

 

5장 - 견고한 애플리케이션 만들기(Service, DI, Test)

Chapter 5 의 내용을 "마우스 위치 로거" 소스를 분석하며 진행.

  • 재사용 가능한 로직 서비스의 개념
  • 의존성 주입의 개념과 활용방법
  • 테스트로 애플리케이션을 견고하게 만드는 방법
  • 크롬 개발자 도구를 사용한 디버깅 방법

5.1 서비스(Service)

  1. 컴포넌트는 순수하게 뷰의 생성과 뷰와의 상호작용을 관리하는것이 목적. 

  2. 애플리케이션 전역에서 사용할수 있는 순수한 비지니스 로직을 서비스로 생성.
  3.  소스분석 : 마우스 위치 로거
    1. https://github.com/not-for-me/hb-angular-first/tree/ch5-1 ~ ch5-4 : 컴포넌트, 서비스 생성
  4. 싱글턴으로서의 서비스
    1. 동일한 서비스를 각 컴포넌트에서 new 로 생성하면 서로 다른 인스턴스로 만들어지므로 주의
    2. @Input 데코레이터를 이용해 프로퍼티 바인딩으로 상위 컴포넌트의 인스턴스를 하위 컴포넌트에 전달 가능
    3. ch5-5

      @Input 으로 전달방법
      export class AppComponent {
        title = 'mpl works!';
        logger: MySpecialLoggerService;
      
        constructor() {
          this.logger = new MySpecialLoggerService(LogLevel.INFO);
        }
      }
      
      
      export class MouseTrackZoneComponent implements OnInit {
        @Input() private logger: MySpecialLoggerService;
      ...
      }
      
      //app.component.html
      <h1>{{title}}</h1>
      <mpl-mouse-track-zone [logger]="logger"></mpl-mouse-track-zone>
      
      

5.2 의존성 주입(DI)

  1. Injectable, Inject 데코레이터
    1. @Injectable : 의존성을 주입받을 클래스 임을 명시
    2. @Inject : 주입할 대상의 정보를 선언. 클래스 타입인경우 앵귤러가 타입정보를 추론하여 자동주입.
    3. ch5-6(변경사항) - 의존성 주입으로 개선
  2. providers
    1. ClassProvider

      ClassProvider
      providers: [MySpecialLoggerService] 또는 
      providers: [{ provide: MySpecialLoggerService, useClass: MySpecialLoggerService }]
    2. ValueProvider

      ValueProvider
      providers: [{ provide: LOG_LEVEL_TOKEN, useValue: LogLevel.INFO }]
    3. 그 외 TypeProviderExistingProviderFactoryProvider - 링크문서 참조

    4. ch5-7 (변경사항) - injectionToken 사용
  3. 의존성 주입기 트리
    1. 현재 컴포넌트에 필요한 서비스 클래스가 선언되어 있는 상위 컴포넌트까지 탐색
    2. 개별 컴포넌트에 별도로 providers 정보 제공 가능
    3. ch5-8 (변경사항)
  4. Host, Optional 데코레이터
    1. @Host : 상위 컴포넌트에서 의존성 정보를 찾지 않고 현재 정보를 찾아서 주입 - ch5-9 (변경사항)
    2. @Optional : 의존성 정보가 있을경우에만 주입 - ch5-10 (변경사항)

5.3, 5.4 테스트 및 디버깅

서비스 및 컴포넌트 테스트

  1. ch5-11
  2. ng test  실행

디버깅

  1. 크롬 개발자 도구 이용하여 타입스크립트 코드를 디버깅 가능 (settings - preferences - Enable JavaScript source maps 옵션 체크) 

 

 

 

  • No labels