티스토리 뷰

728x90

0. 이 포스트는 카드 상세 페이지에서 수량을 조절하거나 담겨진 상품을 삭제하는 기능을 작성한다.

 

1. 우선 UI를 약간 변경하여 수량을 조절하는 버튼 2개와 삭제 버튼을 추가한다. 아래 소스의 중앙 부분에 있다.

  1-1 버튼을 수량 좌우에 배치하고 클릭 이벤트가 발생하면 증감을 처리하는 메소드를 지정한다.

  1-2 제거 버튼도 마찬가지로 클릭 시 이벤트를 처리하는 메소드를 지정한다.

  1-3 각 이름이 incrementQuantity, decrementQuantity, removeItem이고 모두 현재 cartItem을 인자로 넘겨준다.

 

<div class="section-content section-content-p30">
  <p class="alert alert-warning" *ngIf="cartItems.length === 0">
    There is no item in your cart
  </p>
  <table class="table table-bordered">
    <tr>
      <th width="20%">Product Image</th>
      <th width="50%">Product Detail</th>
      <th width="30%"></th>
    </tr>
    <tr *ngFor="let item of cartItems">
      <td><img src="{{ item.imageUrl }}" class="img-responsive" width="150px"></td>
      <td>
        <p></p>
        <p><a routerLink="/products/{{ item.id }}">{{item.name}}</a></p>
        <p>{{item.unitPrice}}</p>
      </td>
      <td>
        <div class="items">
          <label>Quantity: &nbsp;</label> 
          
          <span class="btn btn-secondary btn-sm" (click)="incrementQuantity(item)">
            <i class="fas fa-plus"></i>
          </span>
          {{item.quantity}}
          <span class="btn btn-secondary btn-sm" (click)="decrementQuantity(item)">
            <i class="fas fa-minus"></i>
          </span>

        </div>
        <p>Sub-total {{item.unitPrice * item.quantity | currency:'USD'}}</p>
        <a class="primary-btn" (click)="removeItem(item)">Remove</a>
      </td>
    </tr>
    <tr>
      <td colspan="2"></td>
      <td><b>Total Quantity:&nbsp; {{totalQuantity}}</b>
        <p>Shipping FREE</p>
        <b>Total Price: {{totalPrice | currency:'USD'}}</b><br>
        <a href="checkout-page.html" class="primary-btn">Checkout</a>
      </td>
    </tr>
  </table>
</div>

 

2. cart-details.component.html에서 정의한 메소드를 구현한다.

 

import { Component, OnInit } from '@angular/core';
import { CartService } from 'src/app/services/cart.service';
import { CartItem } from 'src/app/common/cart-item';
import { Product } from 'src/app/common/product';

@Component({
  selector: 'app-cart-details',
  templateUrl: './cart-details.component.html',
  styleUrls: ['./cart-details.component.css']
})
export class CartDetailsComponent implements OnInit {

  cartItems: CartItem[] = []
  totalPrice: number = 0.00
  totalQuantity: number = 0

  constructor(private cartService: CartService) { }

  ngOnInit(): void {
    this.cartItems = this.cartService.cartItems
    this.cartService.totalPrice.subscribe(data => this.totalPrice = data)
    this.cartService.totalQuantity.subscribe(data => this.totalQuantity = data)

    this.cartService.computeTotals()
  }

  incrementQuantity(item: CartItem) {
    this.cartService.addToCart(item)
  }

  decrementQuantity(item: CartItem) {
    this.cartService.decrementQuantity(item)
  }

  removeItem(item: CartItem) {
    this.cartService.removeCartItem(item)
  }
}

 

  2-1 incrementQuantity, decrementQuantity는 1씩 줄이고 늘리는 기능이다.

    2-1-1 incrementQuantity는 상품을 담는 addToCart 로직을 동일하게 사용하면 된다.

    2-1-2 이렇게 하려면 CartService의 addToCart를 약간수정해서 Product 대신 CartItem을 받게 변경해야 한다.

    2-1-3 addToCart의 인자를 변경하였기 때문에 아래의 소스처럼 이 메소드를 사용하는 부분도 수정해야 한다.

      2-1-3-1 ProductList, ProductDetails에서 물건추가 할때 아래처럼 CartItem을 전달하는 것으로 수정해야 한다.

 

  addToCart(product: Product) {
    this.cartService.addToCart(new CartItem(product))
  }

 

    2-1-4 decrementQuantity는 주의해야 할 점이 있는데, 현재 수량이 1이면 삭제 후 카트에 존재하면 안된다.

      2-1-4-1 따라서 1보다 클 경우는 단순히 quantity만 하나 줄이고 1이면 삭제를 하는 메소드를 사용하여 삭제한다.

      2-1-4-2 전달되는 CartItem은 카트상세 페이지에 있는 cartItems: CartItem[]의 하나의 요소이고

        2-1-4-2-1 이 요소는  CartService의 cartItems: CartItem[]를 참조하고 있다.  초기화 메소드에서 할당하고 있다.

        2-1-4-2-2 즉 다 연결되어 있어서 하나 바꾸면 다 바뀐다. call by reference이다.

        2-1-4-2-3 그래서 서비스의 decrementQuantity에서 item.quantity-- 만으로 충분하다.

 

    2-1-5 삭제도 마찬가지인데 splice로 삭제를 하면 실제 연동되어 있는 배열이 바뀐다. 즉 다 그 상품이 삭제된다.

      2-1-5-1 주의 할 점은 splice는 immutable을 구현하기 위해 새로운 배열을 생성하여 반환하기 때문에 

      2-1-5-2 반환된 값을 사용하면 CartDetails와 CartService의 카트물건정보가 각각의 객체를 관리하게 된다.

      2-1-5-3 그래서 절대로 반환값을 사용해서는 안된다.

 

  2-2 removeItem은 인자로 받은 CartItem을 삭제하는 기능이다.

    2-2-1 아래를 보면 삭제기능을 CartService에서 별도의 메소드를 만들었다.

    2-2-2 사용자가 상품을 삭제하는 버튼을 눌렀을 때 동작하거나 수량이 1인 상품의 수량을 줄인 경우 동작한다.

  

  2-3 또 한 가지 주의 할 점은 computeTotals() 호출되는 시점이다.

    2-3-1 값이 변경된 경우 반드시 Subject 전달을 의뢰하기 위해 computeTotals 실행해야 한다.

 

import { Injectable } from '@angular/core';
import { CartItem } from '../common/cart-item';
import { Subject } from 'rxjs';
import { Product } from '../common/product';

@Injectable({
  providedIn: 'root'
})
export class CartService {
  
  cartItems: CartItem[] = []
  
  totalPrice: Subject<number> = new Subject()
  totalQuantity: Subject<number> = new Subject()
  
  constructor() { }
  
  addToCart(cartItem: CartItem) {

    let item: CartItem = undefined
    let alreadyIn: boolean = false;
    item = this.cartItems.find(item=> item.id === cartItem.id)

    alreadyIn = item !== undefined;

    if (alreadyIn) {
      item.quantity++
    } else {
      this.cartItems.push(cartItem)
    }

    this.computeTotals();
  }

  computeTotals() {
    this.totalPrice.next(
      this.cartItems
        .map(item => item.quantity * item.unitPrice)
        .reduce((acc, eachTotal) => acc + eachTotal, 0)
    );

    this.totalQuantity.next(
      this.cartItems
        .map(item => item.quantity)
        .reduce((acc, quantity) => acc + quantity, 0)
    );
  }

  decrementQuantity(item: CartItem) {
    if (item.quantity > 1) {
      item.quantity--
      this.computeTotals()
    } else {
      this.removeCartItem(item)
    }
    
  }

  removeCartItem(item: CartItem) {
    const index = this.cartItems.findIndex(product=> product.id === item.id)

    if (index > -1) {
      this.cartItems.splice(index, 1) 
      this.computeTotals()
    }
  }
}

 

3. 결과화면이다.

 

728x90
댓글