4. 사용해보기: 데이터 다루기
이 가이드 문서는 Angular 애플리케이션 시작하기 튜토리얼의 네비게이션 다음 단계를 다룬다. 현재 온라인 쇼핑몰로 애플리케이션에는 상품과 관련된 화면이 상품 목록과 상품 상세정보 화면, 이렇게 두 화면이 존재한다. 그리고 사용자가 상품 목록에서 상품의 이름을 클릭하면 URL이 변경되면서 상세정보 화면으로 이동한다.
이번에는 이런 단계로 진행하면서 장바구니 기능을 구현해 보자:
- 상품 상세정보 화면에 Buy 버튼을 추가한다. 이 버튼은 해당 상품을 장바구니 서비스가 관리하는 배열에 추가하는 버튼이다.
- 장바구니 컴포넌트를 추가한다. 이 컴포넌트는 장바구니에 담긴 상품 목록을 표시한다.
- 주문 컴포넌트를 추가한다. 이 컴포넌트는 Angular가 제공하는
HttpClient
로.json
파일에서 데이터를 불러와서 장바구니에 담긴 상품의 배송 가격을 표시한다.
1. 장바구니 서비스 생성하기
Angular에서 이야기하는 서비스는 의존성 주입 시스템으로 애플리케이션 어디에라도 주입되는 클래스 인스턴스를 의미한다.
지금까지는 사용자가 상품의 상세정보를 확인할 수 있으며 상품을 공유하거나 가격이 변경되었을 때 알림을 보내는 기능은 임시로 구현했다.
이번에는 사용자가 장바구니에 상품을 추가하는 기능을 만들어 보자. 이 섹션에서는 Buy 버튼을 추가하고 장바구니에 담긴 상품을 관리할 수 있도록 장바구니 서비스를 구현해 보자.
1) 장바구니 서비스 정의하기
장바구니 서비스를 생성하려면 app
폴더에 마우스 오른쪽 버튼을 클릭하고 Angular Generator
를 선택한 다음에 Service
를 선택하면 된다. 이 서비스의 이름은 cart
라고 지정한다.
import { Injectable } from "@angular/core";
@Injectable({
providedIn: "root",
})
export class CartService {
constructor() {}
}
CartService
클래스 안에 items
프로퍼티를 정의한다. 이 프로퍼티는 장바구니에 담긴 상품을 저장하는 배열이다.
import { Product } from "./products";
/* . . . */
export class CartService {
items: Product[] = [];
/* . . . */
}
장바구니에 상품을 추가하는 메서드와 장바구니에 담긴 상품 목록을 반환하는 메서드, 장바구니를 비우는 메서드를 정의한다.
export class CartService {
items: Product[] = [];
/* . . . */
addToCart(product: Product) {
this.items.push(product);
}
getItems() {
return this.items;
}
clearCart() {
this.items = [];
return this.items;
}
/* . . . */
}
addToCart()
메서드는items
배열에 상품을 추가한다.getItems()
메서드는 장바구니에 담긴 상품을 갯수와 함께 반환한다.clearCart()
메서드는 장바구니를 비우고 빈 배열을 반환한다.
2) 장바구니 서비스 활용하기
이번 섹션에서는 CartService
를 활용해서 장바구니에 상품을 추가해 보자.
product-details.component.ts
파일에서 장바구니 서비스를 불러온다.
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { Product, products } from "../products";
import { CartService } from "../cart.service";
장바구니 서비스를 constructor()
에 추가해서 의존성으로 주입한다.
export class ProductDetailsComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private cartService: CartService
) {}
}
컴포넌트에 addToCart()
메서드를 추가한다. 이 메서드는 현재 사용자가 보는 상품을 장바구니에 추가하는 메서드이다.
export class ProductDetailsComponent implements OnInit {
addToCart(product: Product) {
this.cartService.addToCart(product);
window.alert("Your product has been added to the cart!");
}
}
addToCart()
메서드는 이런 동작을 한다:
- 현재
product
를 인자로 받는다. CartService
의addToCart()
메서드를 사용해서 인자로 받은 상품을 장바구니에 추가한다.- 장바구니에 상품이 추가되었다는 메시지를 화면에 표시한다.
product-details.component.html
파일을 열어서 Buy 버튼을 추가하고 이 버튼의 click
이벤트를 addToCart()
메서드와 바인딩한다. 아래 코드는 상품의 상세정보 화면 템플릿에 Buy 버튼을 추가한 코드이다.
<h2>Product Details</h2>
<div *ngIf="product">
<h3>{{ product.name }}</h3>
<h4>{{ product.price | currency }}</h4>
<p>{{ product.description }}</p>
<button (click)="addToCart(product)">Buy</button>
</div>
브라우저를 새로고침해서 Buy 버튼이 표시되는 것을 확인하고 상품 이름을 클릭해서 상품 상세정보가 제대로 표시되는지 확인해 보자.
이제 Buy 버튼을 클릭해서 화면에 표시된 상품을 장바구니에 추가해 보자. 이 때 안내 메시지가 표시되는 것도 확인해 보자.
2. 장바구니 화면 구성하기
고객이 장바구니를 확인하려면 장바구니 화면을 구현해야 한다. 두 단계로 구현해 보자:
1] 장바구니 컴포넌트를 만들고 이 컴포넌트와 연결되는 라우팅 규칙을 정의한다.
2] 컴포넌트에서 장바구니에 있는 아이템을 화면에 표시한다.
1) 장바구니 컴포넌트 구성하기
장바구니 화면을 구성하려면 ProductDetailsComponent
를 만들었던 것과 비슷한 단계를 거치고 이 컴포넌트와 연결되는 라우팅 규칙을 구성하면 된다.
app
폴더에 마우스 오른쪽 버튼을 클릭하고 Angular Generator
, Component
를 클릭해서 컴포넌트를 생성한다. 이 때 이름은 cart
로 지정한다.
import { Component } from "@angular/core";
@Component({
selector: "app-cart",
templateUrl: "./cart.component.html",
styleUrls: ["./cart.component.css"],
})
export class CartComponent {
constructor() {}
}
이렇게 컴포넌트를 생성하면 StackBlitz가 자동으로 ngOnInit()
메서드를 구성한다. 이번 예제에서는 이 메서드를 무시한다.
app.module.ts
파일을 열고 CartComponent
와 연결되는 라우팅 규칙을 추가한다. 이 때 path
는 cart
로 지정한다.
@NgModule({
imports: [
BrowserModule,
ReactiveFormsModule,
RouterModule.forRoot([
{ path: '', component: ProductListComponent },
{ path: 'products/:productId', component: ProductDetailsComponent },
{ path: 'cart', component: CartComponent },
])
],
declarations: [
AppComponent,
TopBarComponent,
ProductListComponent,
ProductAlertsComponent,
ProductDetailsComponent,
CartComponent,
],
Checkout 버튼이 /cart
URL로 연결되도록 수정한다. top-bar.component.html
파일에서 routerLink
디렉티브를 연결해서 /cart
로 이동하면 된다.
<a routerLink="/cart" class="button fancy-button">
<i class="material-icons">shopping_cart</i>Checkout
</a>
Checkout 버튼을 클릭하고 CartComponent
가 제대로 동작하는지 확인해 보자. URL이 https://getting-started.stackblitz.io/cart
와 같은 모양이 되면서 "cart works!" 문구가 표시되면 된다. 이 때 getting-started.stackblitz.io
부분은 StackBlitz 프로젝트에 따라 다를 수 있다.
2) 장바구니 목록 표시하기
이번 섹션에서는 장바구니 서비스를 활용해서 장바구니에 담긴 상품 목록을 표시해 보자.
cart.component.ts
파일에서 cart.service.ts
파일에 정의된 CartService
심볼을 불러온다.
import { Component } from "@angular/core";
import { CartService } from "../cart.service";
CartComponent
의 constructor()
에 CartService
를 의존성으로 주입한다.
export class CartComponent {
constructor(private cartService: CartService) {}
}
장바구니 목록을 저장할 items
프로퍼티를 정의한다.
export class CartComponent {
items = this.cartService.getItems();
constructor(private cartService: CartService) {}
}
이 코드를 보면 CartService
의 getItems()
메서드를 활용해서 상품 목록을 가져오는 것을 확인할 수 있다. 이 메서드는 cart.service.ts
를 생성할 때 정의했다.
장바구니 컴포넌트 템플릿을 수정한다. <div>
엘리먼트에 *ngFor
디렉티브를 적용해서 장바구니에 담긴 상품마다 이름과 가격을 표시한다. 여기까지 작업하면 CartComponent
의 템플릿이 이렇게 구성된다.
<h3>Cart</h3>
<div class="cart-item" *ngFor="let item of items">
<span>{{ item.name }} {{ item.price | currency }}</span>
</div>
장바구니 컴포넌트가 제대로 동작하는지 확인해 보자:
- My Store를 클릭한다.
- 상품의 이름을 클릭해서 상세정보가 표시되는지 확인한다.
- Buy 버튼을 클릭해서 장바구니에 상품을 추가해 보자.
- Checkout 버튼을 클릭해서 장바구니에 상품이 제대로 들어갔는지 확인한다.
Note
- 서비스에 대해 더 자세하게 알아보려면 서비스와 의존성 주입 가이드 문서를 참고한다.
3. 배송가격 받아오기
서버는 스트림 형태로 데이터를 반환하기도 한다. 이런 스트림 형태의 데이터는 원하는 대로 변형하기 쉽기 때문에 활용하기도 좋다. Angular가 기본으로 제공하는 HttpClient
는 외부 API에 접근해서 스트림 형태로 데이터를 받아오는 서비스이다.
이번 섹션에서는 HttpClient
를 사용해서 외부 파일에 있는 배송가격을 받아와 보자.
StackBlitz에서 생성한 애플리케이션에는 assets/shipping.json
파일에 배송가격 데이터가 준비되어 있다. 이 데이터를 불러와서 장바구니에 있는 상품 목록과 연결해 보자.
[
{
"type": "Overnight",
"price": 25.99
},
{
"type": "2-Day",
"price": 9.99
},
{
"type": "Postal",
"price": 2.99
}
]
1) AppModule
에 HttpClient
등록하기
Angular 애플리케이션에 HttpClient
를 사용하려면 애플리케이션에 HttpClientModule
을 등록해야 한다.
Angular HttpClientModule
을 등록하면 애플리케이션 전역에서 HttpClient
서비스를 자유롭게 활용할 수 있다.
app.module.ts
파일에서 @angular/common/http
패키지로 제공되는 HttpClientModule
를 로드한다. 이 심볼 외에도 import
구문은 더 있지만 아래 코드에서는 생략했다. 다른 import
구문은 지우지 않는다.
Angular HttpClient
프로바이더를 전역 범위에 등록하려면 HttpClientModule
을 AppModule의 @NgModule()
내의 imports
배열에 추가하면 된다.
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
ReactiveFormsModule,
RouterModule.forRoot([
{ path: "", component: ProductListComponent },
{ path: "products/:productId", component: ProductDetailsComponent },
{ path: "cart", component: CartComponent },
]),
],
declarations: [
AppComponent,
TopBarComponent,
ProductListComponent,
ProductAlertsComponent,
ProductDetailsComponent,
CartComponent,
],
bootstrap: [AppComponent],
})
export class AppModule {}
2) CartService
에서 HttpClient
사용하기
그 다음에는 서비스가 데이터를 받아올 수 있도록 HttpClient
를 의존성으로 주입하면 된다.
cart.service.ts
파일에서 @angular/common/http
패키지로 제공되는 HttpClient
심볼을 로드한다.
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Product } from "./products";
HttpClient
를 CartService
의 constructor()
에 의존성으로 주입한다.
export class CartService {
items: Product[] = [];
constructor(private http: HttpClient) {}
/* . . . */
}
3) CartService
에서 배송가격 받아오기
shipping.json
파일에 있는 배송가격 데이터를 불러오려면 HttpClient
의 get()
메서드를 사용하면 된다.
cart.service.ts
파일의 clearCart()
메서드 아래 getShippingPrices()
메서드를 추가하고 이 메서드에 HttpClient
의 get()
메서드를 이렇게 구현한다.
export class CartService {
/* . . . */
getShippingPrices() {
return this.http.get<{ type: string; price: number }[]>(
"/assets/shipping.json"
);
}
}
Note
- Angular
HttpClient
에 대해 더 자세하게 알아보려면 클라이언트-서버 통신 문서를 참고한다.
4. 배송 컴포넌트 생성하기
애플리케이션에 배송가격 데이터를 불러올 수 있다면, 이 데이터를 표시할 화면도 필요하다.
app
폴더에 마우스 오른쪽 버튼을 클릭하고 Angular Generator
, Component
를 클릭해서 컴포넌트를 생성한다. 이 때 컴포넌트 이름은 shipping
이라고 지정한다.
import { Component } from "@angular/core";
@Component({
selector: "app-shipping",
templateUrl: "./shipping.component.html",
styleUrls: ["./shipping.component.css"],
})
export class ShippingComponent {
constructor() {}
}
app.module.ts
파일에 배송 컴포넌트로 연결되는 라우팅 규칙을 추가한다. path
에는 shipping
을 지정하고 컴포넌트는 ShippingComponent
를 연결하면 된다.
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
ReactiveFormsModule,
RouterModule.forRoot([
{ path: "", component: ProductListComponent },
{ path: "products/:productId", component: ProductDetailsComponent },
{ path: "cart", component: CartComponent },
{ path: "shipping", component: ShippingComponent },
]),
],
declarations: [
AppComponent,
TopBarComponent,
ProductListComponent,
ProductAlertsComponent,
ProductDetailsComponent,
CartComponent,
ShippingComponent,
],
bootstrap: [AppComponent],
})
export class AppModule {}
아직은 배송 컴포넌트로 연결되는 링크가 없기 때문에 컴포넌트가 동작하는 것을 확인하려면 미리보기 화면에 직접 URL을 입력하면 된다. https://getting-started.stackblitz.io/shipping
라고 입력해 보자. getting-started.stackblitz.io
부분은 StackBlitz 프로젝트에 따라 다를 수 있다.
1) ShippingComponent
에서 CartService
활용하기
이번 섹션에서는 ShippingComponent
에서 HTTP 통신을 활용해서 shipping.json
파일에 있는 데이터를 불러와 보자.
shipping.component.ts
파일에서 CartService
를 불러온다.
import { Component } from "@angular/core";
import { CartService } from "../cart.service";
장바구니 서비스를 ShippingComponent
의 constructor()
에 의존성으로 주입한다.
CartService
의 getShippingPrices()
메서드로 shippingCosts
프로퍼티 값을 할당한다.
export class ShippingComponent {
shippingCosts = this.cartService.getShippingPrices();
}
ShippingComponent
템플릿을 수정해서 배송 타입과 가격을 표시한다. 이 때 async
파이프를 사용해 보자.
<h3>Shipping Prices</h3>
<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
<span>{{ shipping.type }}</span>
<span>{{ shipping.price | currency }}</span>
</div>
async
파이프는 스트림으로 전달된 데이터의 마지막 값을 반환하며, 이 동작은 컴포넌트가 존재하는 동안 계속 실행된다. 그리고 Angular가 컴포넌트를 종료하면 async
파이프도 함께 자동으로 종료된다.
Note
async
파이프에 대해 더 자세하게 알아보려면 AsyncPipe API 문서를 참고한다.
CartComponent
화면에서 ShippingComponent
화면으로 이동하는 링크를 추가한다.
<h3>Cart</h3>
<p>
<a routerLink="/shipping">Shipping Prices</a>
</p>
<div class="cart-item" *ngFor="let item of items">
<span>{{ item.name }}</span>
<span>{{ item.price | currency }}</span>
</div>
Checkout
버튼을 클릭해서 장바구니 목록을 변경해 보자. 애플리케이션 코드가 변경되면 미리보기 화면도 갱신되면서 장바구니가 비워진다.
링크를 클릭해서 배송 컴포넌트로 이동해 보자.