feature: add to cart

- make the cart service dependable on BehavorialSubject, migrated from
siganls
- implement add to cart service
This commit is contained in:
kusowl 2026-03-11 19:00:24 +05:30
parent 27a04c6458
commit ad957efcf0
8 changed files with 50 additions and 36 deletions

View File

@ -32,7 +32,7 @@
style="anchor-name: --anchor-2"
>
<lucide-angular [img]="CartIcon" class="w-5" />
<span class="absolute top-0 text-xs ml-1">{{ cartItemCount() }}</span>
<span class="absolute top-0 text-xs ml-1">{{ cartItemCount | async }}</span>
</button>
</div>
</div>
@ -53,7 +53,7 @@
</ul>
<app-cart
[cart]="cartItem()"
[cart]="(cartItem$ | async)!"
id="popover-2"
class="dropdown"
popover

View File

@ -1,13 +1,16 @@
import { Component, computed, inject } from "@angular/core";
import { Component, inject } from "@angular/core";
import { LucideAngularModule, Search, ShoppingCart, User } from "lucide-angular";
import { RouterLink } from "@angular/router";
import { AuthService, AuthState } from "../../../features/auth/services/auth-service";
import { CartService } from "@app/core/services/cart-service";
import { Cart } from "@app/shared/components/cart/cart";
import { CartModel } from "@app/core/models/cart.model";
import { map } from "rxjs";
import { AsyncPipe } from "@angular/common";
@Component({
selector: "app-header",
imports: [LucideAngularModule, RouterLink, Cart],
imports: [LucideAngularModule, RouterLink, Cart, AsyncPipe],
templateUrl: "./header.html",
styleUrl: "./header.css",
})
@ -19,6 +22,6 @@ export class Header {
readonly cartService = inject(CartService);
protected readonly AuthState = AuthState;
cartItem = this.cartService.cartItem;
cartItemCount = computed(() => this.cartItem().itemsCount ?? 0);
cartItem$ = this.cartService.cartItem$;
cartItemCount = this.cartItem$.pipe(map((cart: CartModel) => cart.itemsCount ?? 0));
}

View File

@ -4,6 +4,7 @@ import { API_URL } from "../tokens/api-url-tokens";
import { CartItemModel, CartItemRequest, CartModel } from "../models/cart.model";
import { AuthService, AuthState } from "@app/features/auth/services/auth-service";
import { Cart } from "@app/shared/components/cart/cart";
import { BehaviorSubject, tap } from "rxjs";
@Injectable({
providedIn: "root",
@ -14,21 +15,23 @@ export class CartService {
private http = inject(HttpClient);
private apiUrl = inject(API_URL);
cartItem = signal<CartModel>({} as CartModel);
private _cartItem = new BehaviorSubject<CartModel>({} as CartModel);
cartItem$ = this._cartItem.asObservable();
constructor() {
effect(() => {
if (this.authService.isAuthenticated()) {
this.fetchCart();
} else {
this.cartItem.set({} as CartModel);
this._cartItem.next({} as CartModel);
}
});
}
fetchCart() {
return this.http.get<CartModel>(this.apiUrl + "/cart").subscribe({
next: (data) => this.cartItem.set(data),
next: (data) => this._cartItem.next(data),
error: (error: HttpErrorResponse) => {
if (error.status === 401) {
this.authService.purgeAuth();
@ -38,11 +41,21 @@ export class CartService {
});
}
addToCart(data: CartItemRequest) {
return this.http
.post<CartModel>(this.apiUrl + "/cart", data)
.pipe(tap((updatedCart: CartModel) => this._cartItem.next(updatedCart)));
}
updateCart(data: CartItemRequest) {
return this.http.patch<CartModel>(this.apiUrl + "/cart", data);
return this.http
.patch<CartModel>(this.apiUrl + "/cart", data)
.pipe(tap((updatedCart: CartModel) => this._cartItem.next(updatedCart)));
}
removeFromCart(productId: number) {
return this.http.delete<CartModel>(this.apiUrl + "/cart", { body: { productId: productId } });
return this.http
.delete<CartModel>(this.apiUrl + "/cart", { body: { productId: productId } })
.pipe(tap((updatedCart: CartModel) => this._cartItem.next(updatedCart)));
}
}

View File

@ -21,7 +21,7 @@
<p class="font-medium text-lg">Rs. {{ product.actualPrice }}</p>
</div>
<div>
<button class="btn btn-primary p-3" title="Add to cart">
<button (click)="addToCart($event)" class="btn btn-primary p-3" title="Add to cart">
<lucide-angular [img]="ShoppingCartIcon" class="w-4" />
</button>
</div>

View File

@ -3,6 +3,7 @@ import { ProductModel } from "../../../../core/models/product.model";
import { Router } from "@angular/router";
import { FavoriteButton } from "../../../../shared/components/favorite-button/favorite-button";
import { LucideAngularModule, ShoppingCart } from "lucide-angular";
import { CartService } from "@app/core/services/cart-service";
@Component({
selector: "app-product-card",
@ -13,10 +14,16 @@ import { LucideAngularModule, ShoppingCart } from "lucide-angular";
export class ProductCard {
readonly router = inject(Router);
readonly ShoppingCartIcon = ShoppingCart;
readonly cartService = inject(CartService);
@Input() product!: ProductModel;
goToProductDetails() {
this.router.navigate(["/products", this.product.slug]);
}
addToCart(event: Event) {
event.stopPropagation();
this.cartService.addToCart({ productId: this.product.id, quantity: 1 }).subscribe();
}
}

View File

@ -90,28 +90,16 @@
<div class="lg:col-span-3 flex flex-col gap-6">
<div class="card">
<div class="flex justify-between items-start mb-6">
<div class="flex items-baseline gap-2">
<span class="text-xl text-gray-400 line-through decoration-1"
>Rs.{{ product()?.actualPrice }}</span
>
<span class="text-3xl font-bold text-gray-900">Rs.{{ product()?.listPrice }}</span>
<span class="text-xs text-gray-400 ml-1"></span>
<div class="">
<p class="text-sm text-gray-400 line-through decoration-1">
Rs.{{ product()?.listPrice }}
</p>
<p class="text-3xl font-bold text-gray-900">Rs.{{ product()?.actualPrice }}</p>
</div>
</div>
<div class="flex mb-6 overflow-hidden">
<button class="px-4 py-2 btn btn-ghost rounded-r-none!"></button>
<input
type="text"
value="1"
class="w-12 text-center border-y border-gray-300"
readonly
/>
<button class="px-4 py-2 btn btn-ghost rounded-l-none!">+</button>
</div>
<div class="flex flex-col gap-3">
<button class="w-full btn btn-ghost">Add to Cart</button>
<button (click)="addToCart()" class="w-full btn btn-ghost">Add to Cart</button>
<button class="w-full btn btn-primary">Buy Now</button>
</div>
</div>

View File

@ -3,6 +3,7 @@ import { ProductModel } from "../../../core/models/product.model";
import { ProductService } from "../services/product-service";
import { LucideAngularModule, Heart, ArrowRight, ArrowLeft } from "lucide-angular";
import { FavoriteButton } from "../../../shared/components/favorite-button/favorite-button";
import { CartService } from "@app/core/services/cart-service";
@Component({
selector: "app-show-product",
@ -17,6 +18,7 @@ export class ShowProduct {
ArrowRightIcon = ArrowRight;
ArrowLeftIcon = ArrowLeft;
productService = inject(ProductService);
cartService = inject(CartService);
product = signal<ProductModel | null>(null);
activeImageIndex: WritableSignal<number> = signal(0);
totalImageCount: number = 0;
@ -29,9 +31,14 @@ export class ShowProduct {
});
}
addToCart() {
this.cartService.addToCart({ productId: this.product()!.id, quantity: 1 }).subscribe();
}
nextImage() {
this.activeImageIndex.update((index) => (index + 1) % this.totalImageCount);
}
prevImage() {
this.activeImageIndex.update(
(index) =>

View File

@ -26,9 +26,7 @@ export class Cart {
this.cartService
.updateCart(cartItem)
.pipe(finalize(() => this.isLoading.set(false)))
.subscribe((cartdata) => {
this.cart = cartdata;
});
.subscribe();
}
removeProduct(productId: number) {
@ -37,8 +35,6 @@ export class Cart {
this.cartService
.removeFromCart(productId)
.pipe(finalize(() => this.isLoading.set(false)))
.subscribe((cartData) => {
this.cart = cartData;
});
.subscribe();
}
}