Obsah článku / Contents
Príchod mesiáša: NgRx Signal Store - Migrácia a prax (Časť 2)

Kapitola 4: Pros & Cons (honest assessment)

NgRx Signal Store Pros ✅

typescript
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 ❌

typescript
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 perfect

When to use what?

typescript
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.

typescript
// 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: Minimal

From NgRx Store to Signal Store

Zlá správa: Difficult! Complete rewrite.

typescript
// 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 feature

Kapitola 6: Advanced patterns

Pattern 1: Custom Features (reusability)

typescript
// 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

typescript
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

typescript
// 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

typescript
// ❌ 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 gradually

Lesson 3: Team training is crucial

typescript
// 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 principles

Epilóg: Is this really the messiah?

Honest answer: For most projects, YES! 🎉

typescript
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:

typescript
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. ❤️

Všetky články