Príchod mesiáša: NgRx Signal Store - Migrácia a prax (Časť 2)
Kapitola 4: Pros & Cons (honest assessment)
NgRx Signal Store Pros ✅
const pros = {
simplicity: "Minimal boilerplate",
performance: "Faster than observables",
composability: "Infinite feature composition",
typeScript: "Excellent type inference",
learning: "Easy to learn",
migration: "Easy from Component Store",
modern: "Uses latest Angular features",
functional: "Functional programming FTW",
noAsyncPipes: "Direct template access",
autoTracking: "Automatic change detection"
};
// Developer happiness: 📈📈📈NgRx Signal Store Cons ❌
const cons = {
devTools: "No Redux DevTools (yet)",
timeTravel: "No time-travel debugging",
ecosystem: "Smaller ecosystem than NgRx Store",
migration: "Can't migrate from NgRx Store easily",
documentation: "Still growing",
community: "Smaller than NgRx Store",
maturity: "Relatively new (2024)",
breaking: "API might change"
};
// Reality check: Nothing is perfectWhen to use what?
function chooseStateManagement(project: Project): Solution {
// Massive enterprise app with complex state
if (project.size === 'massive' && project.needsTimeTravel) {
return 'NgRx Store (Redux)';
// You need DevTools, time-travel, the whole shebang
}
// Medium to large app
if (project.size >= 'medium' && project.developers > 3) {
return 'NgRx Signal Store';
// Perfect balance of power and simplicity
}
// Small app, simple state
if (project.size === 'small' && project.developers <= 2) {
return 'Pure Signals or Signal Store';
// Keep it simple
}
// Legacy app, already using Component Store
if (project.isLegacy && project.uses === 'ComponentStore') {
return 'Gradually migrate to Signal Store';
// Easy migration path
}
// Tiny app
if (project.size === 'tiny') {
return 'Just use Signals';
// Don't over-engineer
}
}Kapitola 5: Migration strategies
From Component Store to Signal Store
Dobrá správa: Easy migration! API is similar.
// BEFORE: Component Store
@Injectable()
export class UserStore extends ComponentStore<UserState> {
readonly users$ = this.select(state => state.users);
readonly addUser = this.updater((state, user: User) => ({
...state,
users: [...state.users, user]
}));
readonly loadUsers = this.effect((trigger$: Observable<void>) =>
trigger$.pipe(
switchMap(() => this.userService.getUsers()),
tap(users => this.patchState({ users }))
)
);
}
// AFTER: Signal Store (straightforward conversion)
export const UserStore = signalStore(
withState<UserState>({ users: [] }),
withComputed((store) => ({
// users are already signals, no need for selector
})),
withMethods((store, userService = inject(UserService)) => ({
addUser(user: User) {
store.update(state => ({
users: [...state.users, user]
}));
},
loadUsers: rxMethod<void>(
pipe(
switchMap(() => userService.getUsers()),
tap(users => store.update({ users }))
)
)
}))
);
// Migration time: ~30 minutes per store
// Breaking changes: MinimalFrom NgRx Store to Signal Store
Zlá správa: Difficult! Complete rewrite.
// You have:
// - actions/
// - reducers/
// - effects/
// - selectors/
// You need to:
// 1. Understand current state structure
// 2. Rewrite everything in Signal Store
// 3. Update all components
// 4. Remove old files
// Time: Days to weeks
// Pain level: High
// Worth it?: Maybe (depends on app size)
// Recommendation: Do it gradually, feature by featureKapitola 6: Advanced patterns
Pattern 1: Custom Features (reusability)
// Create reusable feature
export function withLoading() {
return signalStoreFeature(
withState({ loading: false }),
withMethods((store) => ({
setLoading(loading: boolean) {
store.update({ loading });
}
}))
);
}
// Use in multiple stores
export const UserStore = signalStore(
withState({ users: [] }),
withLoading(), // Reusable!
withMethods(/* ... */)
);
export const ProductStore = signalStore(
withState({ products: [] }),
withLoading(), // Same feature!
withMethods(/* ... */)
);
// DRY principle achieved! 🎉Pattern 2: Entity management
import { withEntities, setEntity, updateEntity } from '@ngrx/signals/entities';
export const ProductStore = signalStore(
withEntities<Product>(), // Magic entity features!
withComputed((store) => ({
// Auto-generated selectors
selectedProduct: computed(() =>
store.entities().find(p => p.id === store.selectedId())
)
})),
withMethods((store) => ({
// Entity helpers
addProduct: (product: Product) =>
store.setEntity(product),
updateProduct: (id: number, changes: Partial<Product>) =>
store.updateEntity(id, changes),
// Built-in entity management!
}))
);Pattern 3: Multi-store composition
// Local + Global stores working together
export const GlobalAuthStore = signalStore(
{ providedIn: 'root' },
withState({ user: null, token: null })
);
export const LocalCartStore = signalStore(
withState({ items: [] }),
withMethods((store, auth = inject(GlobalAuthStore)) => ({
checkout() {
const user = auth.user(); // Access global store!
const items = store.items();
// Process checkout
}
}))
);Kapitola 7: Real-world lessons
Lesson 1: Signal Store is NOT always the answer
// ❌ Don't use Signal Store for this
@Component({
template: `<button (click)="toggle()">{{ isOpen }}</button>`
})
class SimpleToggle {
isOpen = signal(false); // Just use signal!
toggle() {
this.isOpen.update(v => !v);
}
}
// Over-engineering alert! 🚨Lesson 2: Migration takes time
Week 1: "Let's migrate to Signal Store!"
Week 2: "This is taking longer than expected..."
Week 3: "Why did we start this?!"
Week 4: "Ok, it's starting to work..."
Week 5: "Actually this is nice..."
Week 6: "I love Signal Store!" ❤️
Lesson: Plan migration carefully, do it graduallyLesson 3: Team training is crucial
// Junior developer's first Signal Store
export const Store = signalStore(
withState({ data: [] }),
withMethods((store) => ({
// ❌ Mutation! (bad)
addItem(item) {
store.data().push(item); // NOPE!
},
// ✅ Immutable update (good)
addItemCorrect(item) {
store.update(state => ({
data: [...state.data, item]
}));
}
}))
);
// Lesson: Train team on immutability principlesEpilóg: Is this really the messiah?
Honest answer: For most projects, YES! 🎉
const verdict = {
for: {
smallApps: "Perfect",
mediumApps: "Perfect",
largeApps: "Very good",
massiveApps: "Consider NgRx Store first"
},
benefits: [
"90% less boilerplate",
"Better performance",
"Easier to learn",
"Modern Angular idioms",
"Great developer experience"
],
tradeoffs: [
"No Redux DevTools (yet)",
"Smaller ecosystem",
"Relatively new"
],
conclusion: "The benefits far outweigh the tradeoffs for most projects"
};
console.log("Signal Store: The messiah we were waiting for? YES!");Migration roadmap:
Phase 1: New features
→ Use Signal Store immediately
→ No migration needed
→ Build experience
Phase 2: Refactoring
→ Convert Component Store to Signal Store
→ Easy migration
→ Gradual process
Phase 3: Legacy code
→ Consider NgRx Store migration
→ Only if really needed
→ Might not be worth it
Phase 4: Profit
→ Less code to maintain
→ Happier developers
→ Better performance
→ 🎉Záver: The future is signals
Before Signal Store:
Developer: "I need state management"
*looks at options*
Developer: "NgRx Store? 5 files per feature..."
Developer: "Component Store? Better but still RxJS..."
Developer: "Pure Signals? Too simple..."
Developer: *sadness*After Signal Store:
Developer: "I need state management"
*uses Signal Store*
Developer: "One file, minimal boilerplate, signals everywhere"
Developer: "THIS IS PERFECT!" ✨The prophecy:
"A signal was sent from the heavens, bringing simplicity to the land of boilerplate. The developers rejoiced, for their suffering had ended."
"And lo, Signal Store descended upon Angular, and it was good."
Final words:
if (you.are === 'Angular developer') {
learn(SignalStore);
embrace(Signals);
reduce(Boilerplate);
increase(Happiness);
}
// The messiah has arrived. Will you follow?Článok napísal Angular developer, ktorý prežil Redux boilerplate hell, migroval z Component Store na Signal Store, a konečne našiel state management peace.
Stack: Angular 17+, NgRx Signal Store, Signals, TypeScript, RxJS (when needed).
Odporúčanie: Ak začínate nový Angular projekt v 2024+, použite Signal Store. Ak refaktorujete Component Store, migrujte na Signal Store. Ak máte NgRx Store, zvážte to dobre.
P.S.: Redux pattern nie je mŕtvy. Ale pre väčšinu projektov, Signal Store je lepší. Fight me. (Actually, don't. I'll just convince you with code examples.) 😅
P.P.S.: NgRx tím - ďakujem za Signal Store. Čakali sme na to roky. ❤️