feat(Day 15) DI demo

parent 6659f1ab
...@@ -700,10 +700,7 @@ ...@@ -700,10 +700,7 @@
<!-- Dependency Injection Demo Section --> <!-- Dependency Injection Demo Section -->
<section class="tw-bg-white tw-rounded-lg tw-shadow-lg tw-p-6"> <section class="tw-bg-white tw-rounded-lg tw-shadow-lg tw-p-6">
<h1 class="tw-text-3xl tw-font-bold tw-text-gray-800 tw-mb-6">🔧 Dependency Injection Demo (Day 15)</h1> <h1 class="tw-text-3xl tw-font-bold tw-text-gray-800 tw-mb-6">🔧 DI Demo (Day 15)</h1>
<p class="tw-text-gray-600 tw-mb-6">
Demo Constructor Injection, Service Sharing, Provider Override và DI Concepts
</p>
<!-- DI Comparison Component --> <!-- DI Comparison Component -->
<div class="tw-mb-8"> <div class="tw-mb-8">
......
...@@ -2,7 +2,7 @@ export interface ProductModel { ...@@ -2,7 +2,7 @@ export interface ProductModel {
id: number; id: number;
sku: string; sku: string;
name: string; name: string;
price: number; price: number;
description?: string; description?: string;
imageUrl?: string; imageUrl?: string;
} }
......
...@@ -3,13 +3,7 @@ import { BehaviorSubject, Observable } from 'rxjs'; ...@@ -3,13 +3,7 @@ import { BehaviorSubject, Observable } from 'rxjs';
import { ProductModel, CartItem } from '../interface/product.interface'; import { ProductModel, CartItem } from '../interface/product.interface';
/** /**
* CartExtService - Alternative implementation of CartService * CartExtService - Extended cart with discount
* Demo việc override provider trong DI
*
* Khác biệt:
* - Có thêm discount calculation
* - Có thêm external API simulation
* - Different logging style
*/ */
@Injectable() @Injectable()
export class CartExtService { export class CartExtService {
...@@ -21,7 +15,7 @@ export class CartExtService { ...@@ -21,7 +15,7 @@ export class CartExtService {
cartCount$: Observable<number> = this.cartCountSubject.asObservable(); cartCount$: Observable<number> = this.cartCountSubject.asObservable();
constructor() { constructor() {
console.log('🌟 CartExtService (Extended) instance được tạo'); console.log('🌟 CartExtService created');
this.loadCartFromStorage(); this.loadCartFromStorage();
} }
......
...@@ -3,33 +3,25 @@ import { BehaviorSubject, Observable } from 'rxjs'; ...@@ -3,33 +3,25 @@ import { BehaviorSubject, Observable } from 'rxjs';
import { ProductModel, CartItem } from '../interface/product.interface'; import { ProductModel, CartItem } from '../interface/product.interface';
/** /**
* CartService - Demo Dependency Injection trong Angular * CartService - Demo Dependency Injection
*
* @Injectable decorator:
* - Đánh dấu class này có thể được inject
* - providedIn: 'root' = singleton cho toàn app
* - Angular DI container sẽ tự động tạo và quản lý instance
*/ */
@Injectable({ @Injectable({
providedIn: 'root' // Singleton pattern - chỉ có 1 instance trong toàn app providedIn: 'root'
}) })
export class CartService { export class CartService {
// BehaviorSubject để theo dõi state changes
private cartItemsSubject = new BehaviorSubject<CartItem[]>([]); private cartItemsSubject = new BehaviorSubject<CartItem[]>([]);
private cartCountSubject = new BehaviorSubject<number>(0); private cartCountSubject = new BehaviorSubject<number>(0);
// Expose as Observable để components có thể subscribe
cartItems$: Observable<CartItem[]> = this.cartItemsSubject.asObservable(); cartItems$: Observable<CartItem[]> = this.cartItemsSubject.asObservable();
cartCount$: Observable<number> = this.cartCountSubject.asObservable(); cartCount$: Observable<number> = this.cartCountSubject.asObservable();
constructor() { constructor() {
console.log('🛒 CartService instance được tạo'); console.log('🛒 CartService created');
// Load from localStorage if exists
this.loadCartFromStorage(); this.loadCartFromStorage();
} }
/** /**
* Thêm sản phẩm vào giỏ hàng * Add product to cart
*/ */
addToCart(product: ProductModel, quantity: number = 1): void { addToCart(product: ProductModel, quantity: number = 1): void {
const currentItems = this.cartItemsSubject.value; const currentItems = this.cartItemsSubject.value;
...@@ -38,33 +30,31 @@ export class CartService { ...@@ -38,33 +30,31 @@ export class CartService {
let updatedItems: CartItem[]; let updatedItems: CartItem[];
if (existingItemIndex >= 0) { if (existingItemIndex >= 0) {
// Sản phẩm đã có trong giỏ - tăng quantity
updatedItems = currentItems.map((item, index) => updatedItems = currentItems.map((item, index) =>
index === existingItemIndex index === existingItemIndex
? { ...item, quantity: item.quantity + quantity } ? { ...item, quantity: item.quantity + quantity }
: item : item
); );
} else { } else {
// Thêm sản phẩm mới
updatedItems = [...currentItems, { product, quantity }]; updatedItems = [...currentItems, { product, quantity }];
} }
this.updateCart(updatedItems); this.updateCart(updatedItems);
console.log(`✅ Đã thêm ${product.name} vào giỏ hàng`); console.log(`✅ Added ${product.name} to cart`);
} }
/** /**
* Xóa sản phẩm khỏi giỏ hàng * Remove product from cart
*/ */
removeFromCart(productId: number): void { removeFromCart(productId: number): void {
const currentItems = this.cartItemsSubject.value; const currentItems = this.cartItemsSubject.value;
const updatedItems = currentItems.filter(item => item.product.id !== productId); const updatedItems = currentItems.filter(item => item.product.id !== productId);
this.updateCart(updatedItems); this.updateCart(updatedItems);
console.log(`🗑️ Đã xóa sản phẩm ID ${productId} khỏi giỏ hàng`); console.log(`🗑️ Removed product ID ${productId}`);
} }
/** /**
* Cập nhật số lượng sản phẩm * Update quantity
*/ */
updateQuantity(productId: number, quantity: number): void { updateQuantity(productId: number, quantity: number): void {
if (quantity <= 0) { if (quantity <= 0) {
...@@ -80,11 +70,11 @@ export class CartService { ...@@ -80,11 +70,11 @@ export class CartService {
); );
this.updateCart(updatedItems); this.updateCart(updatedItems);
console.log(`📝 Đã cập nhật số lượng sản phẩm ID ${productId} = ${quantity}`); console.log(`📝 Updated quantity for product ID ${productId} = ${quantity}`);
} }
/** /**
* Tính tổng tiền * Calculate total
*/ */
calculateTotal(): number { calculateTotal(): number {
return this.cartItemsSubject.value.reduce( return this.cartItemsSubject.value.reduce(
...@@ -94,7 +84,7 @@ export class CartService { ...@@ -94,7 +84,7 @@ export class CartService {
} }
/** /**
* Lấy tổng số sản phẩm trong giỏ * Get total items count
*/ */
getTotalItems(): number { getTotalItems(): number {
return this.cartItemsSubject.value.reduce( return this.cartItemsSubject.value.reduce(
...@@ -104,22 +94,22 @@ export class CartService { ...@@ -104,22 +94,22 @@ export class CartService {
} }
/** /**
* Xóa toàn bộ giỏ hàng * Clear cart
*/ */
clearCart(): void { clearCart(): void {
this.updateCart([]); this.updateCart([]);
console.log('🧹 Đã xóa toàn bộ giỏ hàng'); console.log('🧹 Cart cleared');
} }
/** /**
* Lấy danh sách items hiện tại * Get current items
*/ */
getCurrentItems(): CartItem[] { getCurrentItems(): CartItem[] {
return this.cartItemsSubject.value; return this.cartItemsSubject.value;
} }
/** /**
* Private method để update cart và sync với localStorage * Update cart and sync with localStorage
*/ */
private updateCart(items: CartItem[]): void { private updateCart(items: CartItem[]): void {
this.cartItemsSubject.next(items); this.cartItemsSubject.next(items);
...@@ -128,18 +118,18 @@ export class CartService { ...@@ -128,18 +118,18 @@ export class CartService {
} }
/** /**
* Lưu giỏ hàng vào localStorage * Save to localStorage
*/ */
private saveCartToStorage(items: CartItem[]): void { private saveCartToStorage(items: CartItem[]): void {
try { try {
localStorage.setItem('cart', JSON.stringify(items)); localStorage.setItem('cart', JSON.stringify(items));
} catch (error) { } catch (error) {
console.error('❌ Không thể lưu giỏ hàng vào localStorage:', error); console.error('❌ Cannot save to localStorage:', error);
} }
} }
/** /**
* Load giỏ hàng từ localStorage * Load from localStorage
*/ */
private loadCartFromStorage(): void { private loadCartFromStorage(): void {
try { try {
...@@ -148,10 +138,10 @@ export class CartService { ...@@ -148,10 +138,10 @@ export class CartService {
const items: CartItem[] = JSON.parse(savedCart); const items: CartItem[] = JSON.parse(savedCart);
this.cartItemsSubject.next(items); this.cartItemsSubject.next(items);
this.cartCountSubject.next(this.getTotalItems()); this.cartCountSubject.next(this.getTotalItems());
console.log('📦 Đã load giỏ hàng từ localStorage'); console.log('📦 Loaded cart from localStorage');
} }
} catch (error) { } catch (error) {
console.error('❌ Không thể load giỏ hàng từ localStorage:', error); console.error('❌ Cannot load from localStorage:', error);
} }
} }
} }
...@@ -4,8 +4,7 @@ import { ProductModel } from '../interface/product.interface'; ...@@ -4,8 +4,7 @@ import { ProductModel } from '../interface/product.interface';
import { MOCK_PRODUCTS } from '../const/products.const'; import { MOCK_PRODUCTS } from '../const/products.const';
/** /**
* ProductService - Quản lý dữ liệu sản phẩm * ProductService - Product management
* Demo về cách tạo service với @Injectable
*/ */
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
...@@ -13,17 +12,13 @@ import { MOCK_PRODUCTS } from '../const/products.const'; ...@@ -13,17 +12,13 @@ import { MOCK_PRODUCTS } from '../const/products.const';
export class ProductService { export class ProductService {
constructor() { constructor() {
console.log('🛍️ ProductService instance được tạo'); console.log('🛍️ ProductService created');
} }
/**
* Lấy danh sách tất cả sản phẩm
* Simulate API call với delay
*/
getAllProducts(): Observable<ProductModel[]> { getAllProducts(): Observable<ProductModel[]> {
console.log('📦 Loading products...'); console.log('📦 Loading products...');
return of(MOCK_PRODUCTS).pipe( return of(MOCK_PRODUCTS).pipe(
delay(500) // Simulate network delay delay(100) // Quick loading
); );
} }
...@@ -32,7 +27,7 @@ export class ProductService { ...@@ -32,7 +27,7 @@ export class ProductService {
*/ */
getProductById(id: number): Observable<ProductModel | undefined> { getProductById(id: number): Observable<ProductModel | undefined> {
const product = MOCK_PRODUCTS.find(p => p.id === id); const product = MOCK_PRODUCTS.find(p => p.id === id);
return of(product).pipe(delay(200)); return of(product).pipe(delay(50));
} }
/** /**
...@@ -43,14 +38,10 @@ export class ProductService { ...@@ -43,14 +38,10 @@ export class ProductService {
product.name.toLowerCase().includes(query.toLowerCase()) || product.name.toLowerCase().includes(query.toLowerCase()) ||
product.description?.toLowerCase().includes(query.toLowerCase()) product.description?.toLowerCase().includes(query.toLowerCase())
); );
return of(filteredProducts).pipe(delay(300)); return of(filteredProducts).pipe(delay(50));
} }
/**
* Lấy sản phẩm theo category (mock)
*/
getProductsByCategory(category: string): Observable<ProductModel[]> { getProductsByCategory(category: string): Observable<ProductModel[]> {
// Mock implementation - trong thực tế sẽ filter theo category return of(MOCK_PRODUCTS).pipe(delay(50));
return of(MOCK_PRODUCTS).pipe(delay(400));
} }
} }
<div class="di-comparison-container p-6"> <div class="di-comparison-container p-6">
<div class="header mb-8"> <div class="header mb-8">
<h2 class="text-3xl font-bold text-gray-800 mb-3"> <h2 class="text-3xl font-bold text-gray-800 mb-3">
🔧 Dependency Injection - So Sánh & Demo 🔧 DI Demo
</h2> </h2>
<p class="text-gray-600 text-lg">
Hiểu rõ sự khác biệt giữa Tight Coupling và Dependency Injection
</p>
</div>
<!-- Introduction -->
<div class="intro-section mb-8 p-6 bg-blue-50 border-l-4 border-blue-400 rounded-r-lg">
<h3 class="text-xl font-semibold mb-3">📚 Dependency Injection là gì?</h3>
<div class="space-y-2 text-gray-700">
<p><strong>Dependency Injection (DI)</strong> là một design pattern quan trọng trong lập trình:</p>
<ul class="list-disc list-inside space-y-1 ml-4">
<li><strong>Injector:</strong> Container chứa các API để tạo và quản lý instances</li>
<li><strong>Provider:</strong> "Công thức" để Injector biết cách tạo dependency</li>
<li><strong>Dependency:</strong> Object/service cần được inject vào component</li>
</ul>
</div>
</div>
<!-- Bad vs Good Comparison -->
<div class="comparison-section mb-8">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Bad Example -->
<div class="bad-example bg-red-50 border border-red-200 rounded-lg p-6">
<div class="header-bad mb-4">
<h3 class="text-xl font-semibold text-red-700 mb-2">
❌ Tight Coupling (Không nên làm)
</h3>
</div>
<div class="code-block bg-white border border-red-300 rounded p-4 mb-4">
<pre class="text-sm text-gray-800 whitespace-pre-wrap">{{ badExample }}</pre>
</div>
<div class="problems">
<h4 class="font-semibold text-red-600 mb-2">🚫 Vấn đề:</h4>
<ul class="text-sm text-red-700 space-y-1">
<li>• Khó thay đổi implementation</li>
<li>• Không thể test với mock objects</li>
<li>• Mỗi component có instance riêng</li>
<li>• Vi phạm SOLID principles</li>
<li>• Code cứng nhắc, khó maintain</li>
</ul>
</div>
</div>
<!-- Good Example -->
<div class="good-example bg-green-50 border border-green-200 rounded-lg p-6">
<div class="header-good mb-4">
<h3 class="text-xl font-semibold text-green-700 mb-2">
✅ Dependency Injection (Cách đúng)
</h3>
</div>
<div class="code-block bg-white border border-green-300 rounded p-4 mb-4">
<pre class="text-sm text-gray-800 whitespace-pre-wrap">{{ goodExample }}</pre>
</div>
<div class="benefits">
<h4 class="font-semibold text-green-600 mb-2">✨ Ưu điểm:</h4>
<ul class="text-sm text-green-700 space-y-1">
<li>• Loose coupling giữa các components</li>
<li>• Dễ dàng test với mock objects</li>
<li>• Share state giữa components</li>
<li>• Tuân thủ SOLID principles</li>
<li>• Code linh hoạt, dễ maintain</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Provider Override -->
<div class="override-section mb-8 p-6 bg-yellow-50 border border-yellow-200 rounded-lg">
<h3 class="text-xl font-semibold text-yellow-700 mb-4">
🔄 Provider Override - Thay Đổi Implementation
</h3>
<div class="code-block bg-white border border-yellow-300 rounded p-4 mb-4">
<pre class="text-sm text-gray-800 whitespace-pre-wrap">{{ overrideExample }}</pre>
</div>
<div class="explanation text-yellow-700">
<p class="mb-2">
<strong>Provider Override</strong> cho phép thay đổi implementation mà không cần sửa component code:
</p>
<ul class="list-disc list-inside space-y-1 ml-4">
<li>Development environment: MockCartService</li>
<li>Production environment: ApiCartService</li>
<li>Testing environment: TestCartService</li>
</ul>
</div>
</div> </div>
<!-- Interactive Demo --> <!-- Interactive Demo -->
...@@ -138,80 +46,4 @@ ...@@ -138,80 +46,4 @@
</p> </p>
</div> </div>
</div> </div>
<!-- DI Benefits -->
<div class="benefits-section mb-8">
<h3 class="text-2xl font-semibold text-gray-800 mb-6">🌟 Lợi Ích Của Dependency Injection</h3>
<div class="benefits-grid grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="benefit-card bg-white border border-gray-200 rounded-lg p-6 shadow-sm">
<div class="benefit-icon text-3xl mb-3">🧪</div>
<h4 class="font-semibold text-lg mb-2">Testability</h4>
<p class="text-gray-600">
Dễ dàng mock dependencies trong unit tests, tăng code coverage và reliability.
</p>
</div>
<div class="benefit-card bg-white border border-gray-200 rounded-lg p-6 shadow-sm">
<div class="benefit-icon text-3xl mb-3">🔧</div>
<h4 class="font-semibold text-lg mb-2">Maintainability</h4>
<p class="text-gray-600">
Code loose coupling, dễ modify và extend mà không ảnh hưởng các component khác.
</p>
</div>
<div class="benefit-card bg-white border border-gray-200 rounded-lg p-6 shadow-sm">
<div class="benefit-icon text-3xl mb-3">🔄</div>
<h4 class="font-semibold text-lg mb-2">Flexibility</h4>
<p class="text-gray-600">
Dễ dàng swap implementations cho different environments (dev, test, prod).
</p>
</div>
<div class="benefit-card bg-white border border-gray-200 rounded-lg p-6 shadow-sm">
<div class="benefit-icon text-3xl mb-3"></div>
<h4 class="font-semibold text-lg mb-2">Performance</h4>
<p class="text-gray-600">
Singleton pattern giúp share instances, giảm memory usage và tăng performance.
</p>
</div>
</div>
</div>
<!-- Angular DI Features -->
<div class="angular-features-section">
<h3 class="text-2xl font-semibold text-gray-800 mb-6">🅰️ Angular DI Features</h3>
<div class="features-list space-y-4">
<div class="feature-item bg-white border border-gray-200 rounded-lg p-4 flex items-start gap-4">
<div class="feature-icon text-2xl">🎯</div>
<div>
<h4 class="font-semibold text-lg mb-1">Hierarchical Injection</h4>
<p class="text-gray-600">
DI system có hierarchy từ root → module → component, cho phép override ở mỗi level.
</p>
</div>
</div>
<div class="feature-item bg-white border border-gray-200 rounded-lg p-4 flex items-start gap-4">
<div class="feature-icon text-2xl">🏭</div>
<div>
<h4 class="font-semibold text-lg mb-1">Multiple Provider Types</h4>
<p class="text-gray-600">
Class, Factory, Value, và Existing providers cho flexibility tối đa.
</p>
</div>
</div>
<div class="feature-item bg-white border border-gray-200 rounded-lg p-4 flex items-start gap-4">
<div class="feature-icon text-2xl">🔍</div>
<div>
<h4 class="font-semibold text-lg mb-1">Tree-shakable Services</h4>
<p class="text-gray-600">
providedIn: 'root' cho phép tree-shaking, chỉ bundle services thực sự được sử dụng.
</p>
</div>
</div>
</div>
</div>
</div> </div>
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CartService } from '../../../shared/data-access/service/cart.service'; import { CartService } from '../../../shared/data-access/service/cart.service';
import { CartExtService } from '../../../shared/data-access/service/cart-ext.service';
/** /**
* BadProductComponent - Demo cách KHÔNG NÊN làm (Tight Coupling) * DI Demo Component
* Khởi tạo trực tiếp dependencies, khó test và maintain
*/
class BadProductComponent {
private cartService: CartService;
constructor() {
// ❌ BAD: Tight coupling - tạo dependency trực tiếp
this.cartService = new CartService();
console.log('❌ BadProductComponent: Tạo CartService trực tiếp');
}
addToCart(productId: number): void {
console.log('❌ BadProductComponent: Adding product với tight coupling');
// Logic thêm sản phẩm...
}
// ❌ Vấn đề:
// - Không thể dễ dàng thay đổi implementation
// - Khó test (không mock được)
// - Vi phạm Dependency Inversion Principle
// - Mỗi component sẽ có instance riêng (không share state)
}
/**
* GoodProductComponent - Demo cách ĐÚNG (Dependency Injection)
* Inject dependencies thông qua constructor
*/
class GoodProductComponent {
constructor(private cartService: CartService) {
console.log('✅ GoodProductComponent: CartService được inject');
}
addToCart(productId: number): void {
console.log('✅ GoodProductComponent: Adding product với DI');
// Logic thêm sản phẩm...
}
// ✅ Ưu điểm:
// - Loose coupling
// - Dễ test (có thể mock service)
// - Có thể dễ dàng thay đổi implementation
// - Share state với các component khác
}
/**
* DIComparisonComponent - Demo so sánh Tight Coupling vs Dependency Injection
*/ */
@Component({ @Component({
selector: 'app-di-comparison', selector: 'app-di-comparison',
...@@ -60,123 +13,23 @@ class GoodProductComponent { ...@@ -60,123 +13,23 @@ class GoodProductComponent {
styleUrls: ['./di-comparison.component.scss'] styleUrls: ['./di-comparison.component.scss']
}) })
export class DIComparisonComponent implements OnInit { export class DIComparisonComponent implements OnInit {
// Demo examples
badExample: string = '';
goodExample: string = '';
overrideExample: string = '';
// Inject CartService để demo
constructor(private cartService: CartService) { constructor(private cartService: CartService) {
console.log('🎯 DIComparisonComponent: CartService injected'); console.log('🎯 DIComparisonComponent: CartService injected');
} }
ngOnInit(): void { ngOnInit(): void {
this.setupExamples();
this.demonstrateDI(); this.demonstrateDI();
} }
private setupExamples(): void {
// Bad example code
this.badExample = `
// ❌ CÁCH KHÔNG NÊN LÀM (Tight Coupling)
class ProductComponent {
cartService: CartService;
constructor() {
// Tạo dependency trực tiếp
this.cartService = new CartService();
}
addToCart(product: Product) {
this.cartService.addToCart(product);
}
}
// Vấn đề:
// - Không thể thay đổi implementation
// - Khó test (không mock được)
// - Mỗi component có instance riêng
// - Vi phạm SOLID principles
`;
// Good example code
this.goodExample = `
// ✅ CÁCH ĐÚNG (Dependency Injection)
@Component({
selector: 'app-product',
// ...
})
export class ProductComponent {
constructor(private cartService: CartService) {
// Angular DI container tự động inject
}
addToCart(product: Product) {
this.cartService.addToCart(product);
}
}
// Ưu điểm:
// - Loose coupling
// - Dễ test và mock
// - Share state giữa components
// - Dễ thay đổi implementation
`;
// Override example
this.overrideExample = `
// 🔄 OVERRIDE PROVIDER
@NgModule({
providers: [
{
provide: CartService,
useClass: CartExtService // Thay thế implementation
}
]
})
export class AppModule {}
// Hoặc trong @Injectable
@Injectable({
providedIn: 'root',
useClass: CartExtService
})
export class CartService {}
`;
}
private demonstrateDI(): void { private demonstrateDI(): void {
console.log('🔍 Demonstrating DI concepts...'); console.log('🔍 Demonstrating DI concepts...');
// Demo 1: Service instance
console.log('📦 Current CartService instance:', this.cartService); console.log('📦 Current CartService instance:', this.cartService);
console.log('🔢 Cart total:', this.cartService.calculateTotal()); console.log('🔢 Cart total:', this.cartService.calculateTotal());
// Demo 2: Service methods
this.cartService.getCurrentItems().forEach(item => {
console.log(`🛒 Item in cart: ${item.product.name} x ${item.quantity}`);
});
} }
/**
* Demo manual instantiation (BAD practice)
*/
demoManualInstantiation(): void {
console.log('❌ Demo: Manual instantiation (BAD)');
// Tạo instance mới - KHÔNG được share state
// const manualCartService = new CartService(); // TypeScript error: constructor is private
console.log('❌ Không thể tạo CartService manually - Constructor injection required');
}
/**
* Demo service methods thông qua injected service
*/
demoServiceMethods(): void { demoServiceMethods(): void {
console.log('✅ Demo: Using injected service'); console.log('✅ Demo: Using injected service');
// Sử dụng injected service
const total = this.cartService.calculateTotal(); const total = this.cartService.calculateTotal();
const itemCount = this.cartService.getTotalItems(); const itemCount = this.cartService.getTotalItems();
...@@ -184,58 +37,26 @@ export class CartService {} ...@@ -184,58 +37,26 @@ export class CartService {}
console.log('📦 Item count from injected service:', itemCount); console.log('📦 Item count from injected service:', itemCount);
} }
/** demoManualInstantiation(): void {
* Demo provider override concept console.log('❌ Demo: Manual Instantiation (Not Recommended)');
*/ const manualService = new CartService();
console.log('⚠️ Manual instance:', manualService);
console.log('💸 Problems: Tight coupling, không share state, khó test');
}
demoProviderOverride(): void { demoProviderOverride(): void {
console.log('🔄 Demo: Provider Override Concept'); console.log('🔄 Demo: Provider Override Concept');
console.log('📝 Check console for detailed explanation'); console.log('📝 Current service:', this.cartService.constructor.name);
console.log('🎯 In real app: Can override with different implementations');
// Giải thích trong console console.log(' - MockCartService for testing');
console.log(` console.log(' - PremiumCartService for premium users');
🔧 Provider Override trong Angular DI: console.log(' - OfflineCartService for offline mode');
1. Default Provider:
@Injectable({ providedIn: 'root' })
export class CartService { ... }
2. Override trong NgModule:
@NgModule({
providers: [
{ provide: CartService, useClass: CartExtService }
]
})
3. Override trong Component:
@Component({
providers: [
{ provide: CartService, useClass: CartExtService }
]
})
4. Factory Provider:
{
provide: CartService,
useFactory: (http: HttpClient) => new CartApiService(http),
deps: [HttpClient]
}
5. Value Provider:
{ provide: API_URL, useValue: 'https://api.example.com' }
`);
} }
/**
* Demo singleton pattern
*/
demoSingleton(): void { demoSingleton(): void {
console.log('🔗 Demo: Singleton Pattern in DI'); console.log('🔗 Demo: Singleton Pattern in DI');
console.log('🎯 CartService instance:', this.cartService);
// Injected service là singleton
const service1 = this.cartService;
// Nếu inject ở component khác cũng sẽ là cùng instance
console.log('🎯 CartService instance:', service1);
console.log('🔄 Same instance across components due to providedIn: "root"'); console.log('🔄 Same instance across components due to providedIn: "root"');
console.log('💡 Benefits: Shared state, better performance, less memory');
} }
} }
<div class="product-list-container tw-p-6"> <div class="product-list-container tw-p-6">
<div class="header tw-mb-6"> <div class="header tw-mb-6">
<h2 class="tw-text-2xl tw-font-bold tw-text-gray-800 tw-mb-2"> <h2 class="tw-text-2xl tw-font-bold tw-text-gray-800 tw-mb-2">
🛍️ Danh Sách Sản Phẩm Danh Sách Sản Phẩm
</h2> </h2>
<p class="tw-text-gray-600">
Demo Dependency Injection - Constructor Injection
</p>
</div> </div>
<!-- Loading State --> <!-- Loading State -->
...@@ -75,15 +72,4 @@ ...@@ -75,15 +72,4 @@
<span>Tổng tiền: <strong class="tw-text-blue-600">${{ cartTotal.toFixed(2) }}</strong></span> <span>Tổng tiền: <strong class="tw-text-blue-600">${{ cartTotal.toFixed(2) }}</strong></span>
</div> </div>
</div> </div>
<!-- DI Info -->
<div class="di-info tw-mt-6 tw-p-4 tw-bg-yellow-50 tw-border-l-4 tw-border-yellow-400">
<h4 class="tw-font-semibold tw-text-lg tw-mb-2">🔧 Dependency Injection Info</h4>
<ul class="tw-text-sm tw-text-gray-700 tw-space-y-1">
<li><strong>ProductService</strong> được inject để load dữ liệu</li>
<li><strong>CartService</strong> được inject để quản lý giỏ hàng</li>
<li>✅ Cả hai services đều là <strong>singleton</strong> (providedIn: 'root')</li>
<li>✅ Angular tự động resolve dependencies thông qua constructor</li>
</ul>
</div>
</div> </div>
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
import { ProductService } from '../../../shared/data-access/service/product.service'; import { ProductService } from '../../../shared/data-access/service/product.service';
...@@ -6,16 +6,7 @@ import { CartService } from '../../../shared/data-access/service/cart.service'; ...@@ -6,16 +6,7 @@ import { CartService } from '../../../shared/data-access/service/cart.service';
import { ProductModel } from '../../../shared/data-access/interface/product.interface'; import { ProductModel } from '../../../shared/data-access/interface/product.interface';
/** /**
* ProductListComponent - Demo Constructor Injection * ProductListComponent - Product list with cart
*
* Inject dependencies:
* - ProductService: để load danh sách sản phẩm
* - CartService: để thêm sản phẩm vào giỏ hàng
*
* Lưu ý về DI:
* - Angular tự động inject instances thông qua constructor
* - Không cần khởi tạo manually với 'new'
* - Services được share giữa các components (singleton)
*/ */
@Component({ @Component({
selector: 'app-product-list', selector: 'app-product-list',
...@@ -33,29 +24,16 @@ export class ProductListComponent implements OnInit, OnDestroy { ...@@ -33,29 +24,16 @@ export class ProductListComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
/**
* Constructor Injection
* Angular DI container tự động inject các dependencies
*
* @param productService - Service để quản lý sản phẩm
* @param cartService - Service để quản lý giỏ hàng
*/
constructor( constructor(
private productService: ProductService, private productService: ProductService,
private cartService: CartService private cartService: CartService,
private cdr: ChangeDetectorRef
) { ) {
console.log('🎯 ProductListComponent constructor called'); console.log('🎯 ProductListComponent initialized');
console.log('📦 ProductService injected:', !!this.productService);
console.log('🛒 CartService injected:', !!this.cartService);
} }
ngOnInit(): void { ngOnInit(): void {
console.log('🚀 ProductListComponent ngOnInit');
// Load products sử dụng injected ProductService
this.loadProducts(); this.loadProducts();
// Subscribe to cart changes sử dụng injected CartService
this.subscribeToCartChanges(); this.subscribeToCartChanges();
} }
...@@ -64,12 +42,7 @@ export class ProductListComponent implements OnInit, OnDestroy { ...@@ -64,12 +42,7 @@ export class ProductListComponent implements OnInit, OnDestroy {
this.destroy$.complete(); this.destroy$.complete();
} }
/**
* Load danh sách sản phẩm từ ProductService
*/
private loadProducts(): void { private loadProducts(): void {
console.log('📦 Loading products using injected ProductService...');
this.productService.getAllProducts() this.productService.getAllProducts()
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe({ .subscribe({
...@@ -85,51 +58,51 @@ export class ProductListComponent implements OnInit, OnDestroy { ...@@ -85,51 +58,51 @@ export class ProductListComponent implements OnInit, OnDestroy {
}); });
} }
/**
* Subscribe to cart changes từ CartService
*/
private subscribeToCartChanges(): void { private subscribeToCartChanges(): void {
// Subscribe to cart count
this.cartService.cartCount$ this.cartService.cartCount$
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe((count: number) => { .subscribe((count: number) => {
this.cartCount = count; this.cartCount = count;
console.log('🔄 Cart count updated:', count);
}); });
// Subscribe to cart items để tính total
this.cartService.cartItems$ this.cartService.cartItems$
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe(() => { .subscribe(() => {
this.cartTotal = this.cartService.calculateTotal(); this.cartTotal = this.cartService.calculateTotal();
console.log('💰 Cart total updated:', this.cartTotal);
}); });
} }
/**
* Thêm sản phẩm vào giỏ hàng sử dụng injected CartService
*/
addToCart(product: ProductModel): void { addToCart(product: ProductModel): void {
console.log('🛒 Adding product to cart:', product.name); console.log('🛒 Adding product to cart:', product.name);
console.log('🔍 Current loading state for product', product.id, ':', this.addingToCart[product.id]);
// Prevent multiple clicks
if (this.addingToCart[product.id]) {
console.log('⚠️ Already adding to cart, skipping...');
return;
}
// Set loading state // Set loading state
this.addingToCart[product.id] = true; this.addingToCart[product.id] = true;
console.log('🔄 Set loading state to true for product', product.id);
// Simulate some processing time try {
setTimeout(() => { // Add to cart
// Sử dụng injected CartService
this.cartService.addToCart(product, 1); this.cartService.addToCart(product, 1);
// Reset loading state
this.addingToCart[product.id] = false;
console.log('✅ Product added to cart successfully'); console.log('✅ Product added to cart successfully');
}, 500); } catch (error) {
console.error('❌ Error adding to cart:', error);
}
// Reset loading state after a short delay for UX
setTimeout(() => {
console.log('🔄 Resetting loading state for product', product.id);
this.addingToCart[product.id] = false;
this.cdr.detectChanges(); // Force change detection
console.log('✅ Loading state reset for product', product.id);
}, 200);
} }
/**
* Handle image error
*/
onImageError(event: any): void { onImageError(event: any): void {
event.target.src = 'https://picsum.photos/200/200?random=999'; event.target.src = 'https://picsum.photos/200/200?random=999';
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment