Skip to content

4. 사용해보기: 데이터 다루기


이 가이드 문서는 Angular 애플리케이션 시작하기 튜토리얼의 네비게이션 다음 단계를 다룬다. 현재 온라인 쇼핑몰로 애플리케이션에는 상품과 관련된 화면이 상품 목록과 상품 상세정보 화면, 이렇게 두 화면이 존재한다. 그리고 사용자가 상품 목록에서 상품의 이름을 클릭하면 URL이 변경되면서 상세정보 화면으로 이동한다.


이번에는 이런 단계로 진행하면서 장바구니 기능을 구현해 보자:


  • 상품 상세정보 화면에 Buy 버튼을 추가한다. 이 버튼은 해당 상품을 장바구니 서비스가 관리하는 배열에 추가하는 버튼이다.
  • 장바구니 컴포넌트를 추가한다. 이 컴포넌트는 장바구니에 담긴 상품 목록을 표시한다.
  • 주문 컴포넌트를 추가한다. 이 컴포넌트는 Angular가 제공하는 HttpClient.json 파일에서 데이터를 불러와서 장바구니에 담긴 상품의 배송 가격을 표시한다.


1. 장바구니 서비스 생성하기

Angular에서 이야기하는 서비스는 의존성 주입 시스템으로 애플리케이션 어디에라도 주입되는 클래스 인스턴스를 의미한다.


지금까지는 사용자가 상품의 상세정보를 확인할 수 있으며 상품을 공유하거나 가격이 변경되었을 때 알림을 보내는 기능은 임시로 구현했다.


이번에는 사용자가 장바구니에 상품을 추가하는 기능을 만들어 보자. 이 섹션에서는 Buy 버튼을 추가하고 장바구니에 담긴 상품을 관리할 수 있도록 장바구니 서비스를 구현해 보자.


1) 장바구니 서비스 정의하기

장바구니 서비스를 생성하려면 app 폴더에 마우스 오른쪽 버튼을 클릭하고 Angular Generator를 선택한 다음에 Service를 선택하면 된다. 이 서비스의 이름은 cart라고 지정한다.


src/app/cart.service.ts
import { Injectable } from "@angular/core";

@Injectable({
  providedIn: "root",
})
export class CartService {
  constructor() {}
}


CartService 클래스 안에 items 프로퍼티를 정의한다. 이 프로퍼티는 장바구니에 담긴 상품을 저장하는 배열이다.


src/app/cart.service.ts
import { Product } from "./products";
/* . . . */
export class CartService {
  items: Product[] = [];
  /* . . . */
}


장바구니에 상품을 추가하는 메서드와 장바구니에 담긴 상품 목록을 반환하는 메서드, 장바구니를 비우는 메서드를 정의한다.


src/app/cart.service.ts
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 파일에서 장바구니 서비스를 불러온다.


src/app/product-details/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()에 추가해서 의존성으로 주입한다.


src/app/product-details/product-details.component.ts
export class ProductDetailsComponent implements OnInit {
  constructor(
    private route: ActivatedRoute,
    private cartService: CartService
  ) {}
}


컴포넌트에 addToCart() 메서드를 추가한다. 이 메서드는 현재 사용자가 보는 상품을 장바구니에 추가하는 메서드이다.


src/app/product-details/product-details.component.ts
export class ProductDetailsComponent implements OnInit {
  addToCart(product: Product) {
    this.cartService.addToCart(product);
    window.alert("Your product has been added to the cart!");
  }
}


addToCart() 메서드는 이런 동작을 한다:


  • 현재 product를 인자로 받는다.
  • CartServiceaddToCart() 메서드를 사용해서 인자로 받은 상품을 장바구니에 추가한다.
  • 장바구니에 상품이 추가되었다는 메시지를 화면에 표시한다.


product-details.component.html 파일을 열어서 Buy 버튼을 추가하고 이 버튼의 click 이벤트를 addToCart() 메서드와 바인딩한다. 아래 코드는 상품의 상세정보 화면 템플릿에 Buy 버튼을 추가한 코드이다.


src/app/product-details/product-details.component.html
<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 버튼이 표시되는 것을 확인하고 상품 이름을 클릭해서 상품 상세정보가 제대로 표시되는지 확인해 보자.


001


이제 Buy 버튼을 클릭해서 화면에 표시된 상품을 장바구니에 추가해 보자. 이 때 안내 메시지가 표시되는 것도 확인해 보자.


002


2. 장바구니 화면 구성하기

고객이 장바구니를 확인하려면 장바구니 화면을 구현해야 한다. 두 단계로 구현해 보자:


1] 장바구니 컴포넌트를 만들고 이 컴포넌트와 연결되는 라우팅 규칙을 정의한다.

2] 컴포넌트에서 장바구니에 있는 아이템을 화면에 표시한다.


1) 장바구니 컴포넌트 구성하기

장바구니 화면을 구성하려면 ProductDetailsComponent를 만들었던 것과 비슷한 단계를 거치고 이 컴포넌트와 연결되는 라우팅 규칙을 구성하면 된다.


app 폴더에 마우스 오른쪽 버튼을 클릭하고 Angular Generator, Component를 클릭해서 컴포넌트를 생성한다. 이 때 이름은 cart로 지정한다.


src/app/cart/cart.component.ts
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와 연결되는 라우팅 규칙을 추가한다. 이 때 pathcart로 지정한다.


src/app/app.module.ts
@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로 이동하면 된다.


src/app/top-bar/top-bar.component.html
<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 프로젝트에 따라 다를 수 있다.


003


2) 장바구니 목록 표시하기

이번 섹션에서는 장바구니 서비스를 활용해서 장바구니에 담긴 상품 목록을 표시해 보자.


cart.component.ts 파일에서 cart.service.ts 파일에 정의된 CartService 심볼을 불러온다.


src/app/cart/cart.component.ts
import { Component } from "@angular/core";
import { CartService } from "../cart.service";


CartComponentconstructor()CartService를 의존성으로 주입한다.


src/app/cart/cart.component.ts
export class CartComponent {
  constructor(private cartService: CartService) {}
}


장바구니 목록을 저장할 items 프로퍼티를 정의한다.


src/app/cart/cart.component.ts
export class CartComponent {
  items = this.cartService.getItems();

  constructor(private cartService: CartService) {}
}


이 코드를 보면 CartServicegetItems() 메서드를 활용해서 상품 목록을 가져오는 것을 확인할 수 있다. 이 메서드는 cart.service.ts를 생성할 때 정의했다.


장바구니 컴포넌트 템플릿을 수정한다. <div> 엘리먼트에 *ngFor 디렉티브를 적용해서 장바구니에 담긴 상품마다 이름과 가격을 표시한다. 여기까지 작업하면 CartComponent의 템플릿이 이렇게 구성된다.


src/app/cart/cart.component.html
<h3>Cart</h3>

<div class="cart-item" *ngFor="let item of items">
  <span>{{ item.name }} {{ item.price | currency }}</span>
</div>


장바구니 컴포넌트가 제대로 동작하는지 확인해 보자:


  • My Store를 클릭한다.
  • 상품의 이름을 클릭해서 상세정보가 표시되는지 확인한다.
  • Buy 버튼을 클릭해서 장바구니에 상품을 추가해 보자.
  • Checkout 버튼을 클릭해서 장바구니에 상품이 제대로 들어갔는지 확인한다.


004


Note


3. 배송가격 받아오기

서버는 스트림 형태로 데이터를 반환하기도 한다. 이런 스트림 형태의 데이터는 원하는 대로 변형하기 쉽기 때문에 활용하기도 좋다. Angular가 기본으로 제공하는 HttpClient는 외부 API에 접근해서 스트림 형태로 데이터를 받아오는 서비스이다.


이번 섹션에서는 HttpClient를 사용해서 외부 파일에 있는 배송가격을 받아와 보자.


StackBlitz에서 생성한 애플리케이션에는 assets/shipping.json 파일에 배송가격 데이터가 준비되어 있다. 이 데이터를 불러와서 장바구니에 있는 상품 목록과 연결해 보자.


src/assets/shipping.json
[
  {
    "type": "Overnight",
    "price": 25.99
  },
  {
    "type": "2-Day",
    "price": 9.99
  },
  {
    "type": "Postal",
    "price": 2.99
  }
]


1) AppModuleHttpClient 등록하기

Angular 애플리케이션에 HttpClient를 사용하려면 애플리케이션에 HttpClientModule을 등록해야 한다.


Angular HttpClientModule을 등록하면 애플리케이션 전역에서 HttpClient 서비스를 자유롭게 활용할 수 있다.


app.module.ts 파일에서 @angular/common/http 패키지로 제공되는 HttpClientModule를 로드한다. 이 심볼 외에도 import 구문은 더 있지만 아래 코드에서는 생략했다. 다른 import 구문은 지우지 않는다.


src/app/app.module.ts
import { HttpClientModule } from "@angular/common/http";


Angular HttpClient 프로바이더를 전역 범위에 등록하려면 HttpClientModule을 AppModule의 @NgModule() 내의 imports 배열에 추가하면 된다.


src/app/app.module.ts
@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 심볼을 로드한다.


src/app/cart.service.ts
import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Product } from "./products";


HttpClientCartServiceconstructor()에 의존성으로 주입한다.


src/app/cart.service.ts
export class CartService {
  items: Product[] = [];

  constructor(private http: HttpClient) {}
  /* . . . */
}


3) CartService에서 배송가격 받아오기

shipping.json 파일에 있는 배송가격 데이터를 불러오려면 HttpClientget() 메서드를 사용하면 된다.


cart.service.ts 파일의 clearCart() 메서드 아래 getShippingPrices() 메서드를 추가하고 이 메서드에 HttpClientget() 메서드를 이렇게 구현한다.


src/app/cart.service.ts
export class CartService {
  /* . . . */
  getShippingPrices() {
    return this.http.get<{ type: string; price: number }[]>(
      "/assets/shipping.json"
    );
  }
}


Note


4. 배송 컴포넌트 생성하기

애플리케이션에 배송가격 데이터를 불러올 수 있다면, 이 데이터를 표시할 화면도 필요하다.


app 폴더에 마우스 오른쪽 버튼을 클릭하고 Angular Generator, Component를 클릭해서 컴포넌트를 생성한다. 이 때 컴포넌트 이름은 shipping이라고 지정한다.


src/app/shipping/shipping.component.ts
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를 연결하면 된다.


src/app/app.module.ts
@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를 불러온다.


src/app/shipping/shipping.component.ts
import { Component } from "@angular/core";

import { CartService } from "../cart.service";


장바구니 서비스를 ShippingComponentconstructor()에 의존성으로 주입한다.


src/app/shipping/shipping.component.ts
constructor(private cartService: CartService) { }


CartServicegetShippingPrices() 메서드로 shippingCosts 프로퍼티 값을 할당한다.


src/app/shipping/shipping.component.ts
export class ShippingComponent {
  shippingCosts = this.cartService.getShippingPrices();
}


ShippingComponent 템플릿을 수정해서 배송 타입과 가격을 표시한다. 이 때 async 파이프를 사용해 보자.


src/app/shipping/shipping.component.html
<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


CartComponent 화면에서 ShippingComponent 화면으로 이동하는 링크를 추가한다.


src/app/cart/cart.component.html
<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 버튼을 클릭해서 장바구니 목록을 변경해 보자. 애플리케이션 코드가 변경되면 미리보기 화면도 갱신되면서 장바구니가 비워진다.


005


링크를 클릭해서 배송 컴포넌트로 이동해 보자.


006


References