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
Hide 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 @@
...
@@ -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"
>
...
...
src/app/shared/data-access/interface/product.interface.ts
View file @
77a2cd83
...
@@ -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
;
}
}
...
...
src/app/shared/data-access/service/cart-ext.service.ts
View file @
77a2cd83
...
@@ -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
();
}
}
...
...
src/app/shared/data-access/service/cart.service.ts
View file @
77a2cd83
...
@@ -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 t
o 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 t
o 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
);
}
}
}
}
}
}
src/app/shared/data-access/service/product.service.ts
View file @
77a2cd83
...
@@ -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
(
20
0
));
return
of
(
product
).
pipe
(
delay
(
5
0
));
}
}
/**
/**
...
@@ -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
(
30
0
));
return
of
(
filteredProducts
).
pipe
(
delay
(
5
0
));
}
}
/**
* 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
));
}
}
}
}
src/app/shared/ui/di-comparison/di-comparison.component.html
View file @
77a2cd83
<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"
>
🔧 D
ependency Injection - So Sánh
&
Demo
🔧 D
I
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>
src/app/shared/ui/di-comparison/di-comparison.component.ts
View file @
77a2cd83
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'
);
}
}
}
}
src/app/shared/ui/product-list/product-list.component.html
View file @
77a2cd83
<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>
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
{
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'
;
}
}
...
...
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