티스토리 뷰
Spring Boot + Angular : Shopping Site 만들기 v3 - 4 - 카트 담기 구현 - Rest Repository with JPA
Korean Eagle 2020. 5. 31. 01:500. 이 포스트는 사용자가 세부 페이지에서 담기를 눌렀을 경우에
0-1 카트에 물건이 담기고 현재 카트에 있는 상품의 가격과 수량이 표시 좌측 상단에 표기되도록 하는 내용이다.
1. 동작 방식
1-0 카트 정보는 공유를 위한 CartService에서 관리한다.
1-1 카트에 물건을 담았을 때 카트에 물건이 담기고 그 물건이 포함된 총 가격과 수량이 CartInfo 컴포넌트에 표기된다.
1-2 담는 이벤트가 발생했을 때, CartService의 총가격과 총수량이라는 두 개의 subject는 그 정보를 emit 하게 되고
1-3 이 두 개의 subject를 subscribe 하는 객체는 해당 데이터를 Observable을 통해서 수신하게 된다.
2. CartService 서비스, CartItem 클래스, CartInfoComponent 생성하기
$ ng generate service services/cart
$ ng generate class common/CartItem
$ ng generate component components/cart-info
2-1 CartItem 클래스를 아래처럼 작성한다. 상품정보와 비슷한 데 쓸데없는 것은 빼고 수량 정보는 추가하였다.
2-1-1 수량정보가 주어지지 않으면 수량은 기본 값이 1이다.
2-1-2 product를 받아서 매핑하도록 작성하였다.
import { Product } from './product'
export class CartItem {
id: string
name: string
imageUrl: string
unitPrice: number
quantity: number = 1
constructor(product: Product) {
this.id = product.id
this.name = product.name
this.imageUrl = product.imageUrl
this.unitPrice = product.unitPrice
}
}
2-2 CartService를 아래처럼 작성한다.
2-2-0 카트에 담길 물건 정보를 저장하기 위해 cartItems 속성을 생성한다.
2-2-1 총 가격, 총수량 정보를 보내 낼 2개의 Subject를 생성한다.
2-2-2 addToCart 함수는 사용자가 담기 버튼을 눌렀을 때 실행하는 메서드로
2-2-2-1 그 물건이 이미 담긴 물건인지를 확인 후 존재하는 경우 수량만 1 증가시키고
2-2-2-2 새로운 물건 일 경우 cartitems에 포함시킨다.
2-2-3 물건이 추가되었으면 총 가격과 총수량을 계산하여 그 결과 값을 각 Subject는 next 메서드를 통해 보낸다.
2-2-3-1 map은 결과 타입을 변경하고, reduce는 숫자 값을 계산하여 하나의 값으로 돌려주는 배열의 내장함수이다.
2-2-3-2 reduce를 사용할 때, 두 번째 인자 값인 초기화 값을 지정해야 한다. empty 배열의 경우 에러가 발생한다.
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(product: Product) {
let item: CartItem = undefined
let alreadyIn: boolean = false;
item = this.cartItems.find(item=> item.id === product.id)
alreadyIn = item !== undefined;
if (alreadyIn) {
item.quantity++
} else {
this.cartItems.push(new CartItem(product))
}
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)
)
}
}
2-3 CartInfo 컴포넌트를 작성한다.
2-3-1 이 컴포넌트는 아래의 파란 네모 부분으로 app.component.html에서 일부를 잘라서 옮겨온다.
2-3-2 cart-info.component.ts파일이다.
2-3-2-1 중요한 부분은 CartService를 주입받아서 총 가격, 총수량 속성에 subscribe 하는 것이다.
2-3-2-2 이렇게 하면 카트에 물건이 추가되었을 때 발생하는 총 가격, 총 수량 정보를 바로 수신하게 된다.
2-3-2-3 html에서 보여주기 위한 로컬 속성 totalPrice, totalQuantity를 생성하였다.
import { Component, OnInit } from '@angular/core';
import { CartService } from 'src/app/services/cart.service';
@Component({
selector: 'app-cart-info',
templateUrl: './cart-info.component.html',
styleUrls: ['./cart-info.component.css']
})
export class CartInfoComponent implements OnInit {
totalPrice: number = 0.00
totalQuantity: number = 0
constructor(private cartService: CartService) { }
ngOnInit(): void {
this.cartService.totalPrice.subscribe(data=> this.totalPrice = data)
this.cartService.totalQuantity.subscribe(data=> this.totalQuantity = data)
}
}
2-3-3 cart-info.component.html은 다음과 같다.
2-3-3-1 별 다른 내용은 없고 컴포넌트에서 수신한 정보를 가져와 보여주고 있다.
<div class="cart-area d-n">
<a href="shopping-detail.html">
<div class="total">{{totalPrice | currency: 'USD'}} <span> {{totalQuantity}}</span></div>
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
</a>
</div>
2-3-3-2 app.component.html은 cart-info 코드가 옮겨가고 대신 컴포넌트를 사용하는 태그가 들어간다.
<div class="page-wrapper">
<app-product-category-menu></app-product-category-menu>
<!-- PAGE CONTAINER-->
<div class="page-container">
<!-- HEADER DESKTOP-->
<header class="header-desktop">
<div class="section-content section-content-p30">
<div class="container-fluid">
<div class="header-wrap">
<app-product-search></app-product-search>
<app-cart-info></app-cart-info>
</div>
<!-- <div class="account-wrap"></div> -->
</div>
</div>
</header>
<!-- END HEADER DESKTOP-->
<!-- MAIN CONTENT-->
<div class="main-content">
<router-outlet></router-outlet>
</div>
<!-- END MAIN CONTENT-->
</div>
</div>
<!-- END PAGE CONTAINER-->
<footer>
<ul>
<li><a href="#">About Us</a></li>
<li><a href="#">Contact Us</a></li>
<li><a href="#">Help</a></li>
</ul>
</footer>
3. 카트에 물건을 넣는 부분에서 연동하는 코드를 작성한다.
3-1 ProductDetail 컴포넌트에 Add to cart 버튼이 있다.
3-1-1 우선 컴포넌트 코드의 맨 아래에 addToCart 메서드가 추가되었고 현재 상품을 카트 서비스로 담는다.
import { Component, OnInit } from '@angular/core';
import { Product } from 'src/app/common/product';
import { ProductService } from 'src/app/services/product.service';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common'
import { CartService } from 'src/app/services/cart.service';
@Component({
selector: 'app-product-details',
templateUrl: './product-details.component.html',
styleUrls: ['./product-details.component.css']
})
export class ProductDetailsComponent implements OnInit {
product: Product = new Product()
constructor(
private productService: ProductService,
private cartService: CartService,
private route: ActivatedRoute,
private location: Location) { }
ngOnInit(): void {
this.route.paramMap.subscribe(()=> this.getProduct())
}
private getProduct() {
this.productService.getProduct(this.route.snapshot.paramMap.get('id')).subscribe(
data=> this.product = data
)
}
goToLastPage() {
this.location.back()
}
addToCart() {
this.cartService.addToCart(this.product);
}
}
3-1-2 뷰 코드 product-detail.component.html 코드이다.
3-1-2-1 기존 Add to cart 더미 링크를 버튼으로 바꾸고 클릭이벤트 발생 시 addToCart를 호출하도록 설정한다.
<div class="section-content section-content-p30">
<div class="container">
<div class="row">
<div>
<img src="{{product.imageUrl}}" class="img-responsive" width="40%">
<h3>{{product.name}}</h3>
<div class="price">{{product.unitPrice | currency: 'USD'}}</div>
<button class="btn btn-primary" (click)="addToCart()">Add to cart</button>
<h4 class="mt-4">Product Description</h4>
<p>{{product.description}}</p>
<div class="text-right">
<button class="mt-5 btn btn-dark" (click)='goToLastPage()'>Back to Product List</button>
</div>
</div>
</div>
</div>
</div>
4. 결과 화면
'Demos > Shopping mall' 카테고리의 다른 글
- Total
- Today
- Yesterday
- 도커 개발환경 참고
- AWS ARN 구조
- Immuability에 관한 설명
- 자바스크립트 멀티 비동기 함수 호출 참고
- WSDL 참고
- SOAP 컨슈머 참고
- MySql dump 사용법
- AWS Lambda with Addon
- NFC 드라이버 linux 설치
- electron IPC
- mifare classic 강의
- go module 관련 상세한 정보
- C 메모리 찍어보기
- C++ Addon 마이그레이션
- JAX WS Header 관련 stackoverflow
- SOAP Custom Header 설정 참고
- SOAP Custom Header
- SOAP BindingProvider
- dispatcher 사용하여 설정
- vagrant kvm으로 사용하기
- git fork, pull request to the …
- vagrant libvirt bridge network
- python, js의 async, await의 차이
- go JSON struct 생성
- Netflix Kinesis 활용 분석
- docker credential problem
- private subnet에서 outbound IP 확…
- 안드로이드 coroutine
- kotlin with, apply, also 등
- 안드로이드 초기로딩이 안되는 경우
- navigation 데이터 보내기
- 레이스 컨디션 navController
- raylib
- 스프링부트
- jsp
- Security
- 하이버네이트
- Validation
- 설정
- Angular
- 매핑
- Spring
- one-to-many
- Spring Security
- WebMvc
- crud
- login
- spring boot
- XML
- RestTemplate
- one-to-one
- 외부파일
- 스프링
- form
- 상속
- Rest
- Many-To-Many
- 자바
- hibernate
- 설정하기
- 로그인
- mapping
- MYSQL