Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

개발을 하기는 합니다만

[Angular][05] (컴포넌트와 템플릿) 데이터 표시하기 본문

Angular/Angular Official Guide

[Angular][05] (컴포넌트와 템플릿) 데이터 표시하기

jaeyoung-lee 2020. 3. 15. 18:53
본 글은 앵귤러 공식 홈페이지의 DOC 항목을 한국어로 (공부 목적으로) 요약(?)정리(?)한 글임을 밝힙니다.

데이터 표시하기

앵귤러 컴포넌트는 애플리케이션의 데이터 구조를 형성합니다. 컴포넌트에 연결되는 HTML 템플릿은 웹페이지의 컨텍스트에 맞게 데이터를 표시하는 수단을 제공합니다. 컴포넌트의 클래스와 템플릿은 애플리케이션 데이터의 뷰를 형성한다고 할 수 있습니다.

 

데이터 벨류들을 모아 페이지에 반영시키는 것을 데이터 바인딩이라고 부릅니다. 컴포넌트 글래스의 데이터 프로퍼티에 HTML 템플릿 제어권을 연결함으로써 사용자에게 데이터를 보여주거나, 사용자로부터 데이터를 받을 수 있습니다.

 

추가적으로, 디렉티브를 통해서 템플릿에 로직을 더할 수 있습니다. 디렉티브는 앵귤러에게 랜더링 단계에서 페이지를 어떻게 변환, 수정할지 알려주는 역할을 합니다.

 

앵귤러는 템플릿 언어(template language)를 HTML 문법에 다양한 데이터 바인딩과 기능적인 디렉티브 문법을 더한 것으로 정의합니다. 페이지가 렌더링 될 때, 앵귤러는 템플릿 문법을 해석하여, 로직과 현재 데이터 상태에 따라 HTML을 업데이트합니다. 다음장에서 템플릿 문법을 자세히 살펴보기 전에, 몇 가지 예시를 통해 템플릿 문법이 어떻에 작동하는지 보여드리려고 합니다.

 

이 예시에서, 다양한 hero들의 배열을 담는 컴포넌트를 만들게 됩니다. 최종 결과물은 다음과 같습니다. 

 

최종 결과물

라이브 예제 링크 / 다운로드 링크

 

문자 바인딩(interpolation)을 통해 컴포넌트 프로퍼티 표시

컴포넌트 프로퍼티를 표현하는 가장 쉬운 방법은 문자 바인딩을 통해 프로퍼티 이름을 바인딩하는 것입니다. 문자 바인딩은 중첩 중괄호로 표현되는 뷰 템플릿 내부에 프로퍼티의 이름을 넣으면 됩니다.

 

{{ 프로퍼티 이름 }}

 

CLI 명령어인 ng new displaying-data를 사용하여 작업환경과 displaying-data라는 이름을 가진 애플리케이션을 생성하세요.

 

app.component.html 파일을 지우세요. 이번 예시에서 사용하지 않습니다.

 

app.component.tx 파일의 컴포넌트 템플릿과 body를 다음과 같이 수정해주세요.

//src/app/app.component.ts

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

@Component({
  selector: 'app-root',
  template: `
    <h1>{{title}}</h1>
    <h2>My favorite hero is: {{myHero}}</h2>
    `
})
export class AppComponent {
  title = 'Tour of Heroes';
  myHero = 'Windstorm';
}

비어있던 컴포넌트에 title과 myHero라는 두 프로퍼티를 추가하였습니다.

 

템플릿은 중첩 중괄호를 통한 문자 바인딩을 통해 두 컴포넌트 프로퍼티를 표시합니다.

//src/app/app.component.ts (template)

template: `
  <h1>{{title}}</h1>
  <h2>My favorite hero is: {{myHero}}</h2>
  `
템플릿은 여러 줄에 걸쳐 스트링을 선언할 수 있게 함으로써 HTML의 가독성을 올려주는 ECMAScript 2015 표준문법 - 백틱, 역따옴표( ` )를 사용하였습니다. 홑따옴표( ' )와 혼동하지 않도록 주의하세요

앵귤러는 title과 myHero 프로퍼티의 값을 자동으로 받아와 브라우저에 표시합니다. 그리고 해당 프로퍼티가 변경되면 화면을 업데이트 합니다.

조금 자세히 말하면, 사용자 키입력, 타이머 완료, HTTP 요청 응답 등 뷰와 관련된 비동기 이벤트 이후에 화면이 갱신됩니다.

AppComponent 인스턴스를 생성하기 위해 new 키워드를 호출하지 않았다는 것에 주목해주세요. 앵귤러는 자동적으로 인스턴스를 생성합니다.

 

@Component 데코레이터 속의 CSS selector는 엘리먼트의 이름이 <app-root>임을 알려줍니다. 해당 엘리먼트는 index.html 파일의 body 내에 <app-root>로 표현됩니다.

/!-- src/index.html (body) --/

<body>
  <app-root></app-root>
</body>

 

AppComponent 클래스를 부트스트래핑 할 때, 앵귤러는 index.html 에서 <app-root>를 찾아내고, AppComponent 인스턴스를 생성하여, 해당 인스턴스를 <app-root> 내부에 렌더링 합니다.

 

이제 애플리케이션을 실행하면 다음과 같이 title과 hero이름이 표시됩니다.

 

다음 장에서는 앱 개발과정에서 나오는 고민들에 대해 다루겠습니다.

 

템플릿 소스 지정하기

@Component 메타데이터는 어디에서 컴포넌트 템플릿을 찾아야하는지 알려줍니다. 다음과 같은 두 장소에 컴포넌트의 템플릿을 저장할 수 있습니다.

  • @Component 데코레이터의 template 프로퍼티를 사용하여 템플릿을 인라인(inline)으로 정의할 수 있습니다. 인라인 템플릿은 소규모 데모나 테스트에 적합합니다.
  • 개별 HTML 파일을 정의하고 @Component 데코레이터의 templateUrl 프로퍼티에 해당 HTML 파일을 연결 할 수 있습니다. 이 방법은 소규모 데모나 테스트보다 규모가 크고 복잡한 경우 보통 사용되며, 새로운 컴포넌트를 생성할 때 기본적으로 채택되는 방식입니다.

어느 스타일이든, 템플릿의 데이터 바인딩은 컴포넌트의 프로퍼티에 동일하게 접근할 수 있습니다. 여기에서는 템플릿의 길이가 짧고 추가적인 HTML파일 없이도 데모를 간단하게 실행할 수 있어 인라인 템플릿 형식으로 작성했습니다.

기본적으로 Angular CLI 명령어 ng generate component 는 템플릿파일을 포함한 컴포넌트를 생성합니다. "-t"(inlineTemplate=true의 줄입말)옵션을 더함으로써 overriding 할 수 있습니다
//기본 형태
ng generate component 컴포넌트-이름
//약어 사용 (g: generate) (c: component)
ng g c 컴포넌트-이름
//인라인 템플릿 사용
ng generate component 컴포넌트-이름 inlineTemplate=true
//인라인 템플릿, 약어 사용
ng g c 컴포넌트-이름 "-t"

변수 초기화

다음 예시는 클래스 내에서 변수를 선언하며 컴포넌트 변수를 초기화 했습니다.

export class AppComponent {
  title: string;
  myHero: string;

  constructor() {
    this.title = 'Tour of Heroes';
    this.myHero = 'Windstorm';
  }
}

이 방법 대신, 생성자를 사용하여 프로퍼티를 선언 초기화합니다.

 

데이터를 순회하는 로직 더하기: ngFor

앵귤러에서 제공하는 *ngFor 디렉티브는 데이터를 순회하도록 해줍니다. 다음 예시는 배열 프로퍼티 내부의 모든 값을을 디렉티브를 통해 보여줍니다.

 

heroes 리스트를 표시하기 위해, hero 네임을 저장하는 배열을 컴포넌트에 더하고 myHero가 배열의 첫 이름을 표시하도록 재정의하였습니다.

//src/app/app.component.ts (class)

export class AppComponent {
  title = 'Tour of Heroes';
  heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
  myHero = this.heroes[0];
}

이제 앵귤러의 ngFor 디렉티브를 사용하여 heroes 리스트의 각 아이템을 표시합시다.

//src/app/app.component.ts (template)

template: `
  <h1>{{title}}</h1>
  <h2>My favorite hero is: {{myHero}}</h2>
  <p>Heroes:</p>
  <ul>
    <li *ngFor="let hero of heroes">
      {{ hero }}
    </li>
  </ul>
`

위의 UI에서는 HTML의 비순서화 리스트를 사용하기 위하여 ul과 li 태그를 사용했습니다. li내의 *ngFor 엘리먼트는 앵귤러의 "반복자(repeater)" 디렉티브입니다. 이 디렉티브는 li 엘리먼트와 자식 엘리먼트를 "반복자 템플릿(repeater templaet)"으로 사용하게끔 해줍니다.

 

//src/app/app.component.ts (li)

<li *ngFor="let hero of heroes">
  {{ hero }}
</li>
*ngFor 앞에 나오는 별표( * )를 꼭 사용해주세요. 이 표기방식은 템플릿 문법에서 매우 중요합니다.

 

ngFor 이중 중괄호 내부의 hero를 주목해주세요. 템플릿 인풋 변수(tempate input variable)의 좋은 예시입니다. 자세한 내용은 템플릿 문법microsyntax를 참고하세요.

 

리스트의 아이템 수 만큼 li 를 반복 생성하며, li 엘리먼트 내부의 hero 변수를 리스트 순회 과정 중 현재 아이템(hero)에 할당합니다. 앵귤러는 이중 중괄호 내부의 컨텍스트만 변수로 사용합니다.

 

[노트]
변수 이름이 꼭 hero일 필요는 없습니다. 예를 들어, 다음과 같이도 작성할 수 있습니다.
//src/app/app.component.ts (li)

<li *ngFor="let foo of heroes">
  {{ foo }}
</li>
이 경우 ngFor은 배열을 표시해주지만, ngFor은 어떠한 iterable 객체도 순회하여 표시할 수 있습니다.

이제 heroes는 비순서화 리스트로 표현됩니다.

비순서화 문법으로 표현된 heroes

 

데이터를 위한 클래스 생성하기

위의 애플리케이션 코드는 컴포넌트 내부에서 데이터를 직접적으로 정의하는데, 이것은 좋은 방법이 아닙니다. 간단한 데모에서 괜찮은 방법이기는 합니다.

 

현재 바인딩 된 데이터가 간단한 문자의 배열입니다. 하지만 실제 애플리케이션에서 바인딩되는 데이터는 대부분 복잡하게 정의된 객체입니다.

 

이러한 바인딩 방식이 복잡한 객체에 적용되기 위해서, hero 이름 문자열 배열을 Hero 객체 배열로 바꿔야합니다. 이를 위해서는 Hero 클래스가 필요합니다.

ng generate class hero

이 명령어는 hero 클래스를 생성합니다.

//src/app/hero.ts

export class Hero {
  constructor(
    public id: number,
    public name: string) { }
}

// 위 코드는 하단의 코드와 같습니다.
export class Hero {
  public id: number
  public name: string
  constructor(id: number, name: string) {
    this.id = id;
    this.name = name;
  }
}

클래스에 생성자와 id, name 두 프로퍼티를 정의해주세요.

 

클래스가 프로퍼티를 가지지 않은 것처럼 보이지만, 사실 이 클래스는 프로퍼티가 정의되어 있습니다. 생성자 인자에 선언을 하는 것은 프로퍼티를 간편하게 생성하기 위한 Typescipt 문법입니다.

 

생성자의 첫번째 인자를 살펴봅시다.

//src/app/hero.ts (id)

public id: number,

이 간단한 문법은 다음 작업들을 수행합니다.

  • 생성자 인자와 인자의 타입을 선언합니다.
  • 동일한 이름을 가진 public 프로퍼티를 선업합니다.
  • 클래스 인스턴스가 생성될 때 해당 public 프로퍼티에 상응하는 생성자 인자의 값을 할당합니다.

Hero 클래스 사용하기

Hero 클래스를 참조하면, AppComponent.heroes 프로퍼티는 "타입이 선언된(typed)" Hero 객체의 배열을 반환합니다.

//src/app/app.component.ts (heroes)

heroes = [
  new Hero(1, 'Windstorm'),
  new Hero(13, 'Bombasto'),
  new Hero(15, 'Magneta'),
  new Hero(20, 'Tornado')
];
myHero = this.heroes[0];

 

템플릿을 수정합시다. 현재는 hero의 id와 name을 보여주지만, 오직 hero의 name 프로퍼티만을 표시하도록 수정해주세요.

//src/app/app.component.ts (template)

template: `
  <h1>{{title}}</h1>
  <h2>My favorite hero is: {{myHero.name}}</h2>
  <p>Heroes:</p>
  <ul>
    <li *ngFor="let hero of heroes">
      {{ hero.name }}
    </li>
  </ul>
`

앱에 표시되는 결과는 같지만, 코드 자체가 더욱 명확해졌습니다.

 

ngIf를 사용한 조건부 표시

때때로 어플리케이션은 특정 조건을 만족하는 뷰만을 표시해 주어야 합니다.

 

Hero 객체가 세 개 이상일 때 메세지를 표시하도록 예시 코드를 바꾸어봅시다.

 

앵귤러 ngIf 디렉티브는 참/거짓 조건에 기반하여 엘리먼트를 DOM에 추가/제거합니다. 동작을 확인하기 위해서 다음과 같은 코드를 템플릿에 추가해주세요.

//src/app/app.component.ts (message)

<p *ngIf="heroes.length > 3">There are many heroes!</p>
*ngFor의 경우와 같이, *ngIf에서도 별표( * )는 필수적입니다.

완성된 코드는 다음과 같습니다.

//src/app/app.component.ts (template)

template: `
  <h1>{{title}}</h1>
  <h2>My favorite hero is: {{myHero.name}}</h2>
  <p>Heroes:</p>
  <ul>
    <li *ngFor="let hero of heroes">
      {{ hero.name }}
    </li>
  </ul>
  //추가된 항목
  <p *ngIf="heroes.length > 3">There are many heroes!</p>
`

이중 겹따옴표( " ) 내부의 템플릿 표현, heroes.length > 3,은 TypeScript처럼 작동합니다. 컴포넌트 속 heroes 배열이 세개 이상의 객체를 가지면, 앵귤러는 해당 p태그를 DOM에 추가하여 메세지를 나타냅니다. 만약 객체가 세 개 이하이면, 앵귤러는 p 태그를 생략하고 메세지가 보이지 않도록 합니다.

 

자세한 정보는 템플릿 표현식(template expression)을 확인하세요.

앵귤러는 메세지를 보여주거나 숨기는 것이 아닙니다. 앵귤러는 p 태그 엘리먼트를 DOM에 추가/제거합니다. 이러한 방식은 대형 프로젝트에서 다수의 데이터 바인딩을 가진 긴 HTML을 조건적으로 포함시키고 제외시킬 때의 처리속도를 향상시켜줍니다.

위 예시에서 배열은 네 개의 객체를 가지기에, 메세지가 표시됩니다. app.component.ts에서 heroes 배열에 있는 엘리먼트를 지우거나 주석처리하면, 브라우저는 자동으로 갱신되며 메세지가 사라집니다.

 

요약

  • 이중 중괄호를 사용한 문자열 바인딩(interpolation)은 컴포넌트 프로퍼티를 표시합니다.
  • ngFor은 항목, 객체의 배열을 순회하여 보여줍니다.
  • TypeScript 클래스는 컴포넌트의 모델 데이터(model data)를 가공하고 해당 모델의 프로퍼피를 표시합니다. 
  • ngIf는 boolean 표현에 기반하여 HTML조각을 DOM에 추가/제거합니다.

예시 코드의 최종 버전은 다음과 같습니다.

 

//src/app/app.component.ts

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

import { Hero } from './hero';

@Component({
  selector: 'app-root',
  template: `
  <h1>{{title}}</h1>
  <h2>My favorite hero is: {{myHero.name}}</h2>
  <p>Heroes:</p>
  <ul>
    <li *ngFor="let hero of heroes">
      {{ hero.name }}
      </li>
  </ul>
  <p *ngIf="heroes.length > 3">There are many heroes!</p>
`
})
export class AppComponent {
  title = 'Tour of Heroes';
  heroes = [
    new Hero(1, 'Windstorm'),
    new Hero(13, 'Bombasto'),
    new Hero(15, 'Magneta'),
    new Hero(20, 'Tornado')
  ];
  myHero = this.heroes[0];
}
//src/app/hero.ts

export class Hero {
  constructor(
    public id: number,
    public name: string) { }
}
//src/app/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 { }
//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();
}

platformBrowserDynamic().bootstrapModule(AppModule);
Comments