Tính Năng Cửa Hàng Tùy Chỉnh trong NgRx Signal Store
Bạn có thể đã gặp phải tình huống này trước đây - bạn đang thêm một tính năng mới vào ứng dụng và nhận ra rằng bạn cần cùng một logic mà bạn đã viết ở nơi khác. Việc sao chép và dán có vẻ nhanh chóng, nhưng sớm muộn gì cũng dẫn đến mã trùng lặp và khó bảo trì. Trong quản lý trạng thái, cách để tránh vấn đề này là sử dụng các tính năng cửa hàng tùy chỉnh. Chúng cho phép bạn định nghĩa chức năng chung một lần và sau đó áp dụng một cách liền mạch cho nhiều cửa hàng.
Ví dụ Thực Tế
Để thấy cách hoạt động này trong thực tế, hãy tưởng tượng bạn đang xây dựng một ứng dụng Angular để theo dõi người chơi và đội trong một trò chơi cạnh tranh.
Bạn sẽ cần:
- Cửa hàng người chơi – lưu trữ tên và vai trò của người chơi (mid, jungle, top, support, adc).
- Cửa hàng đội – lưu trữ tên đội và số trận thắng.
- Tính năng chia sẻ – cả người chơi và đội đều có một hạng (đồng, bạc, vàng…).
Thay vì sao chép logic xếp hạng trong cả hai cửa hàng, chúng ta có thể tách nó ra thành một tính năng tùy chỉnh và chỉ cần tái sử dụng.
Tạo Tính Năng Chia Sẻ
typescript
// with-rank.feature.ts
import { signalStoreFeature, withState, withComputed } from '@ngrx/signals';
import { computed, Signal } from '@angular/core';
export type Rank = 'bronze' | 'silver' | 'gold';
export interface RankState {
rank: Rank;
}
// tính năng tái sử dụng
export function withRank() {
return signalStoreFeature(
withState<RankState>({ rank: 'bronze' }),
withComputed(({ rank }) => ({
isGold: computed(() => rank() === 'gold'),
}))
);
}
// hàm trợ giúp để dễ dàng cập nhật trạng thái
export function setGold(): RankState {
return { rank: 'gold' };
}
export function setSilver(): RankState {
return { rank: 'silver' };
}
export function setBronze(): RankState {
return { rank: 'bronze' };
}
Dưới đây là những gì chúng ta có:
- trạng thái hạng
- Các hàm trợ giúp như
setGold
,setSilver
vàsetBronze
để cập nhật trạng thái. - Một thuộc tính tính toán
isGold
mà chúng ta có thể sử dụng trong các mẫu.
Cửa Hàng Người Chơi
typescript
// player.store.ts
import { signalStore, withState, withMethods, patchState } from '@ngrx/signals';
import { withRank, setGold } from './with-rank.feature';
type Role = 'mid' | 'jungle' | 'top' | 'adc' | 'support';
export interface PlayerState {
name: string;
role: Role;
}
export const PlayerStore = signalStore(
withState<PlayerState>({ name: 'Faker', role: 'mid' }),
withRank(),
withMethods((store) => ({
promoteToGold() {
patchState(store, setGold());
}
}))
);
Người chơi của chúng ta giờ đây có:
- tên,
- vai trò,
- và nhờ vào
withRank
→ cũng có một hạng.
Cửa Hàng Đội
typescript
// team.store.ts
import { signalStore, withState, withMethods, patchState } from '@ngrx/signals';
import { withRank, setGold } from './with-rank.feature';
export interface TeamState {
teamName: string;
wins: number;
}
export const TeamStore = signalStore(
withState<TeamState>({ teamName: 'T1', wins: 0 }),
withRank(),
withMethods((store) => ({
promoteTeamToGold() {
patchState(store, setGold());
}
}))
);
Đội có:
- teamName,
- số trận thắng,
- và một lần nữa, một hạng.
Sử Dụng PlayerStore
trong Một Component
typescript
// player-card.component.ts
import { Component, inject } from '@angular/core';
import { PlayerStore } from './player.store';
@Component({
selector: 'app-player-card',
template: `
<div class="card">
<h2>{{ playerStore.name() }} — {{ playerStore.role() }}</h2>
<p>Hạng: <strong>{{ playerStore.rank() }}</strong></p>
@if (playerStore.isGold()) {
<p>🏅 Người chơi này là Vàng!</p>
}
<button (click)="playerStore.promoteToGold()">Thăng chức lên Vàng</button>
</div>
`,
})
export class PlayerCardComponent {
playerStore = inject(PlayerStore);
}
Sử Dụng TeamStore
trong Một Component
typescript
// team-card.component.ts
import { Component, inject } from '@angular/core';
import { TeamStore } from './team.store';
@Component({
selector: 'app-team-card',
template: `
<div class="card">
<h2>{{ teamStore.teamName() }}</h2>
<p>Số trận thắng: {{ teamStore.wins() }}</p>
<p>Hạng: <strong>{{ teamStore.rank() }}</strong></p>
@if (teamStore.isGold()) {
<p>🏆 Đội này là Vàng!</p>
}
<button (click)="teamStore.promoteTeamToGold()">Thăng chức đội lên Vàng</button>
</div>
`,
})
export class TeamCardComponent {
teamStore = inject(TeamStore);
}
Tại đây, chúng ta tiêm PlayerStore
hoặc TeamStore
, hiển thị thông tin liên quan và cho phép thăng chức một lần nhấp lên hạng vàng (nếu chỉ có việc xếp hạng trong các trò chơi dễ dàng như vậy 😂).
Lợi Ích Đạt Được
Bằng cách tách hạng thành một tính năng tùy chỉnh, chúng ta đã tránh được việc lặp lại logic trong nhiều cửa hàng. Cả người chơi và đội đều chia sẻ cùng một tính năng, và chúng ta có thể mở rộng nó trong tương lai nếu cần.
Hãy nghĩ về các tính năng cửa hàng tùy chỉnh như các nâng cấp trong một trò chơi, bạn tạo chúng một lần và sau đó sử dụng chúng ở bất cứ đâu cần thiết cho một cú hích thêm.
Những Lợi Ích Chính:
- ✅ Mã sạch hơn
- ✅ Trạng thái & phương thức tái sử dụng
- ✅ Giảm thiểu trùng lặp
Và phần tốt nhất: các cửa hàng của bạn vẫn gọn gàng và tập trung vào các trách nhiệm cụ thể của chúng.
Những Lưu Ý Quan Trọng
- Nên sử dụng các tính năng cửa hàng tùy chỉnh để giảm thiểu mã lặp lại, đặc biệt trong các ứng dụng lớn.
- Cần cẩn thận trong việc đặt tên cho các hàm và biến để đảm bảo tính rõ ràng và dễ hiểu cho người đọc.
Kết Luận
Việc sử dụng các tính năng cửa hàng tùy chỉnh trong NgRx Signal Store không chỉ giúp tổ chức mã của bạn tốt hơn mà còn tạo ra một trải nghiệm phát triển hiệu quả hơn. Hãy thử áp dụng phương pháp này trong các dự án của bạn và cảm nhận sự khác biệt!
Câu Hỏi Thường Gặp
-
Tính năng cửa hàng tùy chỉnh là gì?
Tính năng cửa hàng tùy chỉnh cho phép bạn định nghĩa chức năng chung một lần và tái sử dụng nó trong nhiều cửa hàng khác nhau. -
Lợi ích của việc sử dụng tính năng cửa hàng tùy chỉnh là gì?
Giúp giảm thiểu mã lặp lại, tăng tính tái sử dụng và dễ bảo trì. -
Có thể mở rộng tính năng cửa hàng tùy chỉnh không?
Có, bạn có thể mở rộng và thêm chức năng mới vào các tính năng cửa hàng tùy chỉnh khi cần thiết.