Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
M
Meu-Template-Angular-CSR
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Trần Anh Phú
Meu-Template-Angular-CSR
Commits
77a2cd83
Commit
77a2cd83
authored
Jul 02, 2025
by
Nguyễn Thị Thanh Trúc
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(Day 15) DI demo
parent
6659f1ab
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
73 additions
and
489 deletions
+73
-489
home.component.html
src/app/+home/feature/home.component.html
+1
-4
product.interface.ts
src/app/shared/data-access/interface/product.interface.ts
+1
-1
cart-ext.service.ts
src/app/shared/data-access/service/cart-ext.service.ts
+2
-8
cart.service.ts
src/app/shared/data-access/service/cart.service.ts
+20
-30
product.service.ts
src/app/shared/data-access/service/product.service.ts
+6
-15
di-comparison.component.html
src/app/shared/ui/di-comparison/di-comparison.component.html
+1
-169
di-comparison.component.ts
src/app/shared/ui/di-comparison/di-comparison.component.ts
+15
-194
product-list.component.html
src/app/shared/ui/product-list/product-list.component.html
+1
-15
product-list.component.ts
src/app/shared/ui/product-list/product-list.component.ts
+26
-53
No files found.
src/app/+home/feature/home.component.html
View file @
77a2cd83
...
...
@@ -700,10 +700,7 @@
<!-- Dependency Injection Demo Section -->
<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>
<p
class=
"tw-text-gray-600 tw-mb-6"
>
Demo Constructor Injection, Service Sharing, Provider Override và DI Concepts
</p>
<h1
class=
"tw-text-3xl tw-font-bold tw-text-gray-800 tw-mb-6"
>
🔧 DI Demo (Day 15)
</h1>
<!-- DI Comparison Component -->
<div
class=
"tw-mb-8"
>
...
...
src/app/shared/data-access/interface/product.interface.ts
View file @
77a2cd83
src/app/shared/data-access/service/cart-ext.service.ts
View file @
77a2cd83
...
...
@@ -3,13 +3,7 @@ import { BehaviorSubject, Observable } from 'rxjs';
import
{
ProductModel
,
CartItem
}
from
'../interface/product.interface'
;
/**
* CartExtService - Alternative implementation of CartService
* 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
* CartExtService - Extended cart with discount
*/
@
Injectable
()
export
class
CartExtService
{
...
...
@@ -21,7 +15,7 @@ export class CartExtService {
cartCount$
:
Observable
<
number
>
=
this
.
cartCountSubject
.
asObservable
();
constructor
()
{
console
.
log
(
'🌟 CartExtService
(Extended) instance được tạo
'
);
console
.
log
(
'🌟 CartExtService
created
'
);
this
.
loadCartFromStorage
();
}
...
...
src/app/shared/data-access/service/cart.service.ts
View file @
77a2cd83
...
...
@@ -3,33 +3,25 @@ import { BehaviorSubject, Observable } from 'rxjs';
import
{
ProductModel
,
CartItem
}
from
'../interface/product.interface'
;
/**
* CartService - Demo Dependency Injection trong Angular
*
* @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
* CartService - Demo Dependency Injection
*/
@
Injectable
({
providedIn
:
'root'
// Singleton pattern - chỉ có 1 instance trong toàn app
providedIn
:
'root'
})
export
class
CartService
{
// BehaviorSubject để theo dõi state changes
private
cartItemsSubject
=
new
BehaviorSubject
<
CartItem
[]
>
([]);
private
cartCountSubject
=
new
BehaviorSubject
<
number
>
(
0
);
// Expose as Observable để components có thể subscribe
cartItems$
:
Observable
<
CartItem
[]
>
=
this
.
cartItemsSubject
.
asObservable
();
cartCount$
:
Observable
<
number
>
=
this
.
cartCountSubject
.
asObservable
();
constructor
()
{
console
.
log
(
'🛒 CartService instance được tạo'
);
// Load from localStorage if exists
console
.
log
(
'🛒 CartService created'
);
this
.
loadCartFromStorage
();
}
/**
*
Thêm sản phẩm vào giỏ hàng
*
Add product to cart
*/
addToCart
(
product
:
ProductModel
,
quantity
:
number
=
1
):
void
{
const
currentItems
=
this
.
cartItemsSubject
.
value
;
...
...
@@ -38,33 +30,31 @@ export class CartService {
let
updatedItems
:
CartItem
[];
if
(
existingItemIndex
>=
0
)
{
// Sản phẩm đã có trong giỏ - tăng quantity
updatedItems
=
currentItems
.
map
((
item
,
index
)
=>
index
===
existingItemIndex
?
{
...
item
,
quantity
:
item
.
quantity
+
quantity
}
:
item
);
}
else
{
// Thêm sản phẩm mới
updatedItems
=
[...
currentItems
,
{
product
,
quantity
}];
}
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
{
const
currentItems
=
this
.
cartItemsSubject
.
value
;
const
updatedItems
=
currentItems
.
filter
(
item
=>
item
.
product
.
id
!==
productId
);
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
{
if
(
quantity
<=
0
)
{
...
...
@@ -80,11 +70,11 @@ export class CartService {
);
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
{
return
this
.
cartItemsSubject
.
value
.
reduce
(
...
...
@@ -94,7 +84,7 @@ export class CartService {
}
/**
*
Lấy tổng số sản phẩm trong giỏ
*
Get total items count
*/
getTotalItems
():
number
{
return
this
.
cartItemsSubject
.
value
.
reduce
(
...
...
@@ -104,22 +94,22 @@ export class CartService {
}
/**
*
Xóa toàn bộ giỏ hàng
*
Clear cart
*/
clearCart
():
void
{
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
[]
{
return
this
.
cartItemsSubject
.
value
;
}
/**
*
Private method để update cart và sync với
localStorage
*
Update cart and sync with
localStorage
*/
private
updateCart
(
items
:
CartItem
[]):
void
{
this
.
cartItemsSubject
.
next
(
items
);
...
...
@@ -128,18 +118,18 @@ export class CartService {
}
/**
*
Lưu giỏ hàng và
o localStorage
*
Save t
o localStorage
*/
private
saveCartToStorage
(
items
:
CartItem
[]):
void
{
try
{
localStorage
.
setItem
(
'cart'
,
JSON
.
stringify
(
items
));
}
catch
(
error
)
{
console
.
error
(
'❌
Không thể lưu giỏ hàng và
o localStorage:'
,
error
);
console
.
error
(
'❌
Cannot save t
o localStorage:'
,
error
);
}
}
/**
* Load
giỏ hàng từ
localStorage
* Load
from
localStorage
*/
private
loadCartFromStorage
():
void
{
try
{
...
...
@@ -148,10 +138,10 @@ export class CartService {
const
items
:
CartItem
[]
=
JSON
.
parse
(
savedCart
);
this
.
cartItemsSubject
.
next
(
items
);
this
.
cartCountSubject
.
next
(
this
.
getTotalItems
());
console
.
log
(
'📦
Đã load giỏ hàng từ
localStorage'
);
console
.
log
(
'📦
Loaded cart from
localStorage'
);
}
}
catch
(
error
)
{
console
.
error
(
'❌
Không thể load giỏ hàng từ
localStorage:'
,
error
);
console
.
error
(
'❌
Cannot load from
localStorage:'
,
error
);
}
}
}
src/app/shared/data-access/service/product.service.ts
View file @
77a2cd83
...
...
@@ -4,8 +4,7 @@ import { ProductModel } from '../interface/product.interface';
import
{
MOCK_PRODUCTS
}
from
'../const/products.const'
;
/**
* ProductService - Quản lý dữ liệu sản phẩm
* Demo về cách tạo service với @Injectable
* ProductService - Product management
*/
@
Injectable
({
providedIn
:
'root'
...
...
@@ -13,17 +12,13 @@ import { MOCK_PRODUCTS } from '../const/products.const';
export
class
ProductService
{
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
[]
>
{
console
.
log
(
'📦 Loading products...'
);
return
of
(
MOCK_PRODUCTS
).
pipe
(
delay
(
500
)
// Simulate network delay
delay
(
100
)
// Quick loading
);
}
...
...
@@ -32,7 +27,7 @@ export class ProductService {
*/
getProductById
(
id
:
number
):
Observable
<
ProductModel
|
undefined
>
{
const
product
=
MOCK_PRODUCTS
.
find
(
p
=>
p
.
id
===
id
);
return
of
(
product
).
pipe
(
delay
(
20
0
));
return
of
(
product
).
pipe
(
delay
(
5
0
));
}
/**
...
...
@@ -43,14 +38,10 @@ export class ProductService {
product
.
name
.
toLowerCase
().
includes
(
query
.
toLowerCase
())
||
product
.
description
?.
toLowerCase
().
includes
(
query
.
toLowerCase
())
);
return
of
(
filteredProducts
).
pipe
(
delay
(
30
0
));
return
of
(
filteredProducts
).
pipe
(
delay
(
5
0
));
}
/**
* Lấy sản phẩm theo category (mock)
*/
getProductsByCategory
(
category
:
string
):
Observable
<
ProductModel
[]
>
{
// Mock implementation - trong thực tế sẽ filter theo category
return
of
(
MOCK_PRODUCTS
).
pipe
(
delay
(
400
));
return
of
(
MOCK_PRODUCTS
).
pipe
(
delay
(
50
));
}
}
src/app/shared/ui/di-comparison/di-comparison.component.html
View file @
77a2cd83
<div
class=
"di-comparison-container p-6"
>
<div
class=
"header mb-8"
>
<h2
class=
"text-3xl font-bold text-gray-800 mb-3"
>
🔧 D
ependency Injection - So Sánh
&
Demo
🔧 D
I
Demo
</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>
<!-- Interactive Demo -->
...
...
@@ -138,80 +46,4 @@
</p>
</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>
src/app/shared/ui/di-comparison/di-comparison.component.ts
View file @
77a2cd83
import
{
Component
,
OnInit
}
from
'@angular/core'
;
import
{
CommonModule
}
from
'@angular/common'
;
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)
* 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
* DI Demo Component
*/
@
Component
({
selector
:
'app-di-comparison'
,
...
...
@@ -60,123 +13,23 @@ class GoodProductComponent {
styleUrls
:
[
'./di-comparison.component.scss'
]
})
export
class
DIComparisonComponent
implements
OnInit
{
// Demo examples
badExample
:
string
=
''
;
goodExample
:
string
=
''
;
overrideExample
:
string
=
''
;
// Inject CartService để demo
constructor
(
private
cartService
:
CartService
)
{
console
.
log
(
'🎯 DIComparisonComponent: CartService injected'
);
}
ngOnInit
():
void
{
this
.
setupExamples
();
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
{
console
.
log
(
'🔍 Demonstrating DI concepts...'
);
// Demo 1: Service instance
console
.
log
(
'📦 Current CartService instance:'
,
this
.
cartService
);
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
{
console
.
log
(
'✅ Demo: Using injected service'
);
// Sử dụng injected service
const
total
=
this
.
cartService
.
calculateTotal
();
const
itemCount
=
this
.
cartService
.
getTotalItems
();
...
...
@@ -184,58 +37,26 @@ export class CartService {}
console
.
log
(
'📦 Item count from injected service:'
,
itemCount
);
}
/**
* Demo provider override concept
*/
demoProviderOverride
():
void
{
console
.
log
(
'🔄 Demo: Provider Override Concept'
);
console
.
log
(
'📝 Check console for detailed explanation'
);
// Giải thích trong console
console
.
log
(
`
🔧 Provider Override trong Angular DI:
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]
demoManualInstantiation
():
void
{
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'
);
}
5. Value Provider:
{ provide: API_URL, useValue: 'https://api.example.com' }
`
);
demoProviderOverride
():
void
{
console
.
log
(
'🔄 Demo: Provider Override Concept'
);
console
.
log
(
'📝 Current service:'
,
this
.
cartService
.
constructor
.
name
);
console
.
log
(
'🎯 In real app: Can override with different implementations'
);
console
.
log
(
' - MockCartService for testing'
);
console
.
log
(
' - PremiumCartService for premium users'
);
console
.
log
(
' - OfflineCartService for offline mode'
);
}
/**
* Demo singleton pattern
*/
demoSingleton
():
void
{
console
.
log
(
'🔗 Demo: Singleton Pattern in DI'
);
// 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
(
'🎯 CartService instance:'
,
this
.
cartService
);
console
.
log
(
'🔄 Same instance across components due to providedIn: "root"'
);
console
.
log
(
'💡 Benefits: Shared state, better performance, less memory'
);
}
}
src/app/shared/ui/product-list/product-list.component.html
View file @
77a2cd83
<div
class=
"product-list-container tw-p-6"
>
<div
class=
"header tw-mb-6"
>
<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>
<p
class=
"tw-text-gray-600"
>
Demo Dependency Injection - Constructor Injection
</p>
</div>
<!-- Loading State -->
...
...
@@ -75,15 +72,4 @@
<span>
Tổng tiền:
<strong
class=
"tw-text-blue-600"
>
${{ cartTotal.toFixed(2) }}
</strong></span>
</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>
src/app/shared/ui/product-list/product-list.component.ts
View file @
77a2cd83
import
{
Component
,
OnInit
,
OnDestroy
}
from
'@angular/core'
;
import
{
Component
,
OnInit
,
OnDestroy
,
ChangeDetectorRef
}
from
'@angular/core'
;
import
{
CommonModule
}
from
'@angular/common'
;
import
{
Subject
,
takeUntil
}
from
'rxjs'
;
import
{
ProductService
}
from
'../../../shared/data-access/service/product.service'
;
...
...
@@ -6,16 +6,7 @@ import { CartService } from '../../../shared/data-access/service/cart.service';
import
{
ProductModel
}
from
'../../../shared/data-access/interface/product.interface'
;
/**
* ProductListComponent - Demo Constructor Injection
*
* 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)
* ProductListComponent - Product list with cart
*/
@
Component
({
selector
:
'app-product-list'
,
...
...
@@ -33,29 +24,16 @@ export class ProductListComponent implements OnInit, OnDestroy {
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
(
private
productService
:
ProductService
,
private
cartService
:
CartService
private
cartService
:
CartService
,
private
cdr
:
ChangeDetectorRef
)
{
console
.
log
(
'🎯 ProductListComponent constructor called'
);
console
.
log
(
'📦 ProductService injected:'
,
!!
this
.
productService
);
console
.
log
(
'🛒 CartService injected:'
,
!!
this
.
cartService
);
console
.
log
(
'🎯 ProductListComponent initialized'
);
}
ngOnInit
():
void
{
console
.
log
(
'🚀 ProductListComponent ngOnInit'
);
// Load products sử dụng injected ProductService
this
.
loadProducts
();
// Subscribe to cart changes sử dụng injected CartService
this
.
subscribeToCartChanges
();
}
...
...
@@ -64,12 +42,7 @@ export class ProductListComponent implements OnInit, OnDestroy {
this
.
destroy$
.
complete
();
}
/**
* Load danh sách sản phẩm từ ProductService
*/
private
loadProducts
():
void
{
console
.
log
(
'📦 Loading products using injected ProductService...'
);
this
.
productService
.
getAllProducts
()
.
pipe
(
takeUntil
(
this
.
destroy$
))
.
subscribe
({
...
...
@@ -85,51 +58,51 @@ export class ProductListComponent implements OnInit, OnDestroy {
});
}
/**
* Subscribe to cart changes từ CartService
*/
private
subscribeToCartChanges
():
void
{
// Subscribe to cart count
this
.
cartService
.
cartCount$
.
pipe
(
takeUntil
(
this
.
destroy$
))
.
subscribe
((
count
:
number
)
=>
{
this
.
cartCount
=
count
;
console
.
log
(
'🔄 Cart count updated:'
,
count
);
});
// Subscribe to cart items để tính total
this
.
cartService
.
cartItems$
.
pipe
(
takeUntil
(
this
.
destroy$
))
.
subscribe
(()
=>
{
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
{
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
this
.
addingToCart
[
product
.
id
]
=
true
;
console
.
log
(
'🔄 Set loading state to true for product'
,
product
.
id
);
// Simulate some processing time
setTimeout
(()
=>
{
// Sử dụng injected CartService
try
{
// Add to cart
this
.
cartService
.
addToCart
(
product
,
1
);
console
.
log
(
'✅ Product added to cart successfully'
);
}
catch
(
error
)
{
console
.
error
(
'❌ Error adding to cart:'
,
error
);
}
// Reset loading state
// Reset loading state after a short delay for UX
setTimeout
(()
=>
{
console
.
log
(
'🔄 Resetting loading state for product'
,
product
.
id
);
this
.
addingToCart
[
product
.
id
]
=
false
;
console
.
log
(
'✅
Product added to cart successfully'
);
},
5
00
);
this
.
cdr
.
detectChanges
();
// Force change detection
console
.
log
(
'✅
Loading state reset for product'
,
product
.
id
);
},
2
00
);
}
/**
* Handle image error
*/
onImageError
(
event
:
any
):
void
{
event
.
target
.
src
=
'https://picsum.photos/200/200?random=999'
;
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment