Skip to content

2. 사용해보기: 시작하기


이 튜토리얼은 Angular의 기본 개념을 다룬다. 온라인 쇼핑 앱에 필요한 상품 목록, 장바구니, 주문 폼을 단계별로 만들어 보자.


Angular를 빠르게 사용해보기 위해 이 튜토리얼은 로컬 환경설정을 생략하고 StackBlitz에서 직접 진행한다. StackBlitz는 브라우저 기반의 개발 환경이기 때문에 웹 브라우저에서 바로 애플리케이션을 생성하고 저장할 수 있으며, 자유롭게 공유할 수 있다.


1. 사전지식

이 튜토리얼에서 설명하는 내용을 제대로 이해하려면 이런 내용들에 대해 미리 알고 있는 것이 좋다.



2. 예제 애플리케이션 살펴보기

Angular 애플리케이션은 컴포넌트(component) 단위로 구성한다. 컴포넌트는 화면에서 특정 영역을 담당하며 동작을 처리하는 단위이다.


컴포넌트는 3가지로 구성된다:


  • 컴포넌트 클래스는 데이터를 관리하고 동작을 처리한다.
  • HTML 템플릿은 화면을 구성한다.
  • 컴포넌트용 스타일은 화면이 어떤 모습으로 표시될지 지정한다.


이 가이드 문서에서 다루는 애플리케이션을 구성하는 컴포넌트는 이런 것들이 있다.


  • <app-root>: 다른 컴포넌트를 띄울 수 있도록 제일 먼저 로딩되는 컴포넌트이다.
  • <app-top-bar>: 온라인 샵의 이름과 주문 버튼을 표시한다.
  • <app-product-list>: 상품 목록을 표시한다.
  • <app-product-alerts>: 알림을 표시하는 컴포넌트이다.


001


Note

  • 컴포넌트에 대해 자세하게 알아보려면 컴포넌트 소개 문서를 참고한다.


3. 예제 프로젝트 생성하기

예제 프로젝트를 생성하려면 StackBlitz에 이미 만들어져 있는 프로젝트에 접속한다. 그리고 이렇게 진행하면 된다:


1] StackBlitz에 로그인한다.

2] 프로젝트를 포크한다.

3] 주기적으로 저장한다.


StackBlitz에서 오른쪽 패널을 보면 예제 애플리케이션이 어떻게 실행되는지 확인할 수 있다. 이 애플리케이션은 두 영역으로 구분할 수 있다.


  • 최상단에 온라인 샵 이름이 MyStore라고 표시되며 주문 버튼이 표시된다.
  • 상품 목록의 헤더에 해당하는 Products가 표시된다.


002


그리고 왼쪽 프로젝트 섹션을 보면 이 애플리케이션을 구성하는 소스 파일들을 확인할 수 있다. 개발환경 설정파일과 애플리케이션 환경설정 파일도 확인할 수 있다.


StackBlitz 예제 애플리케이션을 생성하고 나면 StackBlitz 프로젝트에 기본 파일들과 데이터가 있는 것도 확인할 수 있다. 이 튜토리얼을 진행하면서 작업할 파일들은 src 폴더 안에 있다.


Note

  • StackBlitz에 대해 자세하게 알아보려면 StackBlitz 문서를 참고한다.


4. 상품 목록 생성하기

이번 섹션에서는 애플리케이션에 상품 목록을 표시해 보자. 상품 데이터는 products.ts 파일에 있으며, product-list.component.ts 파일에 있는 메서드를 함께 사용할 것이다. 먼저 템플릿에 해당하는 HTML 파일을 수정한다.


product-list 폴더에 있는 템플릿 파일 product-list.component.html을 연다.


<div> 엘리먼트에 구조 디렉티브 *ngFor를 다음과 같이 추가한다.


src/app/product-list/product-list.component.html
<h2>Products</h2>

<div *ngFor="let product of products"></div>


*ngFor를 사용하면 목록에 있는 개별 상품마다 <div> 엘리먼트를 반복할 수 있다.


구조 디렉티브는 엘리먼트를 추가하거나 제거하고, 변형하는 방식으로 DOM 구조를 조작한다.


Note

  • 구조 디렉티브에 대해 자세하게 알아보려면 구조 디렉티브 문서를 참고한다.


<div> 엘리먼트 안에 <h3> 엘리먼트를 추라하고 이 엘리먼트의 내용을 {{ product.name }}라고 작성한다. {{ product.name }}이라는 표현은 Angular가 제공하는 문자열 바인딩(interpolation) 문법이다. 문자열 바인딩 문법({{ }})을 사용하면 프로퍼티 값을 문자열로 렌더링할 수 있다.


src/app/product-list/product-list.component.html
<h2>Products</h2>

<div *ngFor="let product of products">
  <h3>{{ product.name }}</h3>
</div>


이제 미리보기 패널을 보면 목록에 있는 상품마다 상품의 이름이 표시되는 것을 확인할 수 있다.


003


상품의 이름을 클릭했을 때 상세보기 화면으로 전환하려면 {{ product.name }}<a> 엘리먼트로 감싸면 된다.


<a> 엘리먼트의 제목을 지정하기 위해 프로퍼티 바인딩 문법 []를 추가한다:


src/app/product-list/product-list.component.html
<h2>Products</h2>

<div *ngFor="let product of products">
  <h3>
    <a [title]="product.name + ' details'"> {{ product.name }} </a>
  </h3>
</div>


미리보기 패널에서 상품 이름 위에 마우스를 올려보면 상품의 이름에 "details"라는 문자열이 붙어서 표시되는 것을 확인할 수 있다. 프로퍼티 바인딩 문법([ ])을 활용하면 프로퍼티 값을 템플릿 표현식으로 활용할 수 있다.


004


상품 설명을 추가한다. <p> 엘리먼트에 *ngIf 디렉티브를 추가하면 상품 설명이 존재할 때 이 상품의 설명을 DOM에 추가할 수 있다.


src/app/product-list/product-list.component.html
<h2>Products</h2>

<div *ngFor="let product of products">
  <h3>
    <a [title]="product.name + ' details'"> {{ product.name }} </a>
  </h3>

  <p *ngIf="product.description">Description: {{ product.description }}</p>
</div>


이제 미리보기 패널을 보면 목록에 있는 상품마다 상품 이름이 표시되며, 상품 설명이 존재하면 이 설명도 화면에 표시되는 것을 확인할 수 있다. 마지막 상품은 상품 설명이 없기 때문에 설명이 표시되지 않는 것도 확인해 보자. 상품의 설명에 해당하는 프로퍼티가 존재하지 않으면 Angular는 해당 <p> 엘리먼트를 생성하지 않는다.


005


사용자가 상품을 공유할 수 있도록 버튼을 추가한다. 버튼의 click 이벤트를 바인딩해서 product-list.component.ts 파일에 정의된 share() 메서드와 연결한다. <button> 엘리먼트에서 발생하는 (click) 이벤트를 감지하려면 이벤트 이름을 소괄호(( ))로 감싸면 된다.


src/app/product-list/product-list.component.html
<h2>Products</h2>

<div *ngFor="let product of products">
  <h3>
    <a [title]="product.name + ' details'"> {{ product.name }} </a>
  </h3>

  <p *ngIf="product.description">Description: {{ product.description }}</p>

  <button (click)="share()">Share</button>
</div>


이제 개별 상품마다 Share 버튼이 추가된 것을 확인할 수 있다.


006


Share 버튼을 클릭해 보자. "The product has been shared!"라는 알림이 표시되는 것을 확인할 수 있다.


007


Note

  • 템플릿을 수정하면서 Angular 템플릿에서 가장 기본적으로 사용되는 기능을 몇 가지 알아봤다.
  • 더 자세한 내용을 확인하려면 컴포넌트와 템플릿 소개 문서를 참고한다.


5. 자식 컴포넌트로 데이터 전달하기

여기까지 구현하면 상품 목록 화면에는 개별 상품의 이름과 설명이 표시된다. 이 때 표시되는 상품의 목록은 ProductListComponentproducts 프로퍼티에 할당되어 있으며, 이 데이터는 products.ts 파일에 정의된 products 배열에서 불러온 것이다.


다음 단계는 ProductListComponent에 있는 상품 데이터를 활용해서 알림 기능을 구현해 보자. 이 알림 기능은 상품의 가격을 검사해서 가격이 $700 이상이면 Notify Me 버튼을 표시해서 해당 상품이 세일을 진행하면 사용자에게 알리는 용도이다.


이번 섹션에서는 부모 컴포넌트 ProductListComponent에서 데이터를 받는 자식 컴포넌트 ProductAlertsComponent를 만들어 보자.


app 폴더에 마우스 오른쪽 버튼을 클릭하고 Angular Generator 항목을 선택한 후에 product-alerts라는 이름으로 컴포넌트를 생성한다.


008


그러면 컴포넌트를 구성하는 다음 3개 파일이 생성된다:


  • product-alerts.component.ts
  • porduct-alerts.component.html
  • product-alerts.component.css


product-alerts.component.ts 파일을 연다. @Component() 데코레이터는 이 데코레이터가 붙은 클래스가 컴포넌트라는 것을 지정하는 용도로 사용한다. @Component() 데코레이터에는 컴포넌트 셀렉터, 템플릿, 스타일에 해당하는 메타데이터를 지정할 수 있다.


src/app/product-alerts/product-alerts.component.ts
import { Component, OnInit } from "@angular/core";

@Component({
  selector: "app-product-alerts",
  templateUrl: "./product-alerts.component.html",
  styleUrls: ["./product-alerts.component.css"],
})
export class ProductAlertsComponent implements OnInit {
  constructor() {}

  ngOnInit() {}
}


@Component() 데코레이터에서 이런 것들이 중요하다:


  • app-product-alerts라는 selector는 이 컴포넌트가 어떤 태그 이름으로 사용될지 결정한다. 보통 Angular 컴포넌트는 app-이라는 접두사로 시작하고 그 뒤에 컴포넌트 이름을 붙인다.
  • 컴포넌트에 사용될 HTML 파일과 CSS 파일을 지정한다.
  • @Component() 데코레이터 뒤에는 컴포넌트의 동작을 정의할 클래스를 ProductAlertsComponent와 같이 선언한다.


ProductAlertsComponent가 상품 데이터를 받으려면 @angular/core 패키지가 제공하는 Input 심볼을 로드해야 한다.


src/app/product-alerts/product-alerts.component.ts
import { Component, OnInit, Input } from "@angular/core";
import { Product } from "../products";


ProductAlertsComponent 클래스 선언에 product라는 프로퍼티를 선언하고 이 프로퍼티 앞에 @Input() 데코레이터를 지정한다. 프로퍼티에 @Input() 데코레이터를 지정하면 해당 프로퍼티의 값을 부모 컴포넌트에서 받아온다는 것을 의미한다. 이 경우에는 ProductListComponent가 부모 컴포넌트이다.


src/app/product-alerts/product-alerts.component.ts
export class ProductAlertsComponent implements OnInit {
  @Input() product!: Product;
  constructor() {}

  ngOnInit() {}
}


product-alerts.component.html 파일을 열고 상품의 가격이 $700를 넘으면 Notify Me 버튼을 표시하도록 수정한다.


src/app/product-alerts/product-alerts.component.html
<p *ngIf="product && product.price > 700">
  <button>Notify Me</button>
</p>


ProductListComponent의 자식 컴포넌트로 ProductAlertsComponent를 표시하기 위해 product-list.component.html 파일에 <app-product-alerts> 셀렉터를 추가한다.


src/app/app.module.ts
import { ProductAlertsComponent } from './product-alerts/product-alerts.component';

@NgModule({
  declarations: [
    AppComponent,
    TopBarComponent,
    ProductListComponent,
    ProductAlertsComponent,
  ],


그리고 ProductAlertsComponent를 표시할 수 있도록 product-list.component.html 파일에서 ProductListComponent의 자식으로 <app-product-alerts> 셀렉터를 추가한다. 그러면 프로퍼티 바인딩을 통해 해당 상품이 자식 컴포넌트의 입력 프로퍼티로 전달된다.


src/app/product-list/product-list.component.html
<button (click)="share()">Share</button>

<app-product-alerts [product]="product"> </app-product-alerts>


알림 컴포넌트는 상품 목록 컴포넌트에 있는 상품 데이터를 입력 프로퍼티로 받는다. 이 알림 컴포넌트는 상품 가격에 따라 Notify Me 버튼을 표시하는데, Phone XL의 가격이 $700을 넘기 때문에 이 상품에 Notify Me 버튼이 표시되는 것을 확인할 수 있다.


009


6. 부모 컴포넌트로 데이터 전달하기

Notify Me 버튼이 제대로 동작하려면 자식 컴포넌트에서 부모 컴포넌트로 알림을 보내기 위해 데이터를 전달할 수 있어야 한다. 그러면 ProductAlertsComponent는 사용자가 Notify Me 버튼을 클릭했을 때 부모 컴포넌트로 이벤트를 보내야 한다.


Note

  • Angular Generator로 컴포넌트를 새로 만들면 constructor() 메서드가 빈 내용으로 생성되며, OnInit 인터페이스를 확장하는 ngOnInit() 메서드도 함께 생성된다.
  • 지금 단계에서는 이 메서드들을 사용하지 않기 때문에 예제에서 생략했다.


product-alerts.component.ts 파일에 @angular/core 패키지가 제공하는 Output 심볼과 EventEmitter 심볼을 로드한다.


src/app/product-alerts/product-alerts.component.ts
import { Component, Input, Output, EventEmitter } from "@angular/core";
import { Product } from "../products";


컴포넌트 클래스에 notify 프로퍼티를 추가하고 이 프로퍼티에 @Output() 데코레이터를 지정하고 EventEmitter() 인스턴스를 할당한다. ProductAlertsComponent 프로퍼티에 @Output() 데코레이터를 지정하면 ProductAlertsComponentnotify 프로퍼티를 통해 이벤트를 보낼 수 있다.


src/app/product-alerts/product-alerts.component.ts
export class ProductAlertsComponent {
  @Input() product: Product | undefined;
  @Output() notify = new EventEmitter();
}


Note

  • Angular Generator로 컴포넌트를 새로 만들면 constructor() 메서드가 빈 내용으로 생성되며, OnInit 인터페이스를 확장하는 ngOnInit() 메서드도 함께 생성된다.
  • 지금 단계에서는 이 메서드들을 사용하지 않기 때문에 예제에서 생략했다.


product-alerts.component.html 파일에 있는 Notify Me 버튼에 이벤트 바인딩을 연결해서 (click) 이벤트가 발생하면 notify.emit() 메서드를 실행하게 한다.


src/app/product-alerts/product-alerts.component.html
<p *ngIf="product && product.price > 700">
  <button (click)="notify.emit()">Notify Me</button>
</p>


사용자가 버튼을 클릭했을 때 필요한 동작을 구현해 보자. 자식 컴포넌트에서 발생한 이벤트는 자식 컴포넌트 ProductAlertsComponent가 아니라 부모 컴포넌트 ProductListComponent에서 처리해야 한다. product-list.component.ts 파일을 열고 onNotify() 메서드를 추가한다. 이 메서드의 내용은 share() 메서드와 비슷하다.


src/app/product-list/product-list.component.ts
export class ProductListComponent {
  products = products;

  share() {
    window.alert("The product has been shared!");
  }

  onNotify() {
    window.alert("You will be notified when the product goes on sale");
  }
}


ProductAlertsComponent가 보낸 데이터를 ProductListComponent가 받을 수 있도록 수정해 보자. product-list.component.html 파일을 열고 <app-product-alerts>onNotify() 메서드를 바인딩한다. <app-product-alerts>Notify Me가 표시되는 버튼이다.


src/app/product-list/product-list.component.html
<button (click)="share()">Share</button>

<app-product-alerts [product]="product" (notify)="onNotify()">
</app-product-alerts>


Notify Me 버튼을 클릭하면 알림 기능이 동작하면서 "You will be notified when the product goes on sale"라는 메시지가 표시되는 것을 확인할 수 있다.


010


Note

  • 컴포넌트 사이에 통신하는 방법에 대해 자세하게 알아보려면 컴포넌트 통신 문서를 참고한다.

References