Custom Store Features in NgRx Signal Store

You’ve probably faced this situation before – you’re adding a new feature to your app, and you realize you need the same logic you already wrote somewhere else. Copy-pasting feels quick, but it soon leads to duplicated code and harder maintenance. In s…


This content originally appeared on DEV Community and was authored by Duško Perić

You’ve probably faced this situation before – you’re adding a new feature to your app, and you realize you need the same logic you already wrote somewhere else. Copy-pasting feels quick, but it soon leads to duplicated code and harder maintenance. In state management, the way to avoid this problem is with custom store features. They allow you to define shared functionality once and then apply it seamlessly to multiple stores.

To see how this works in practice, imagine building an Angular app to track players and teams in a competitive game.

You’ll need:

  • Player store – stores player’s name and role (mid, jungle, top, support, adc).
  • Team store – stores the team name and number of wins.
  • Shared feature – both players and teams have a rank (bronze, silver, gold…).

Instead of duplicating the rank logic in both stores, we can pull it out into a custom feature and just reuse it.

Create the shared feature

// 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;
}

// reusable feature
export function withRank() {
  return signalStoreFeature(
    withState<RankState>({ rank: 'bronze' }),
    withComputed(({ rank }) => ({
      isGold: computed(() => rank() === 'gold'),
    }))
  );
}

// helper functions to patch state easily
export function setGold(): RankState {
  return { rank: 'gold' };
}

export function setSilver(): RankState {
  return { rank: 'silver' };
}

export function setBronze(): RankState {
  return { rank: 'bronze' };
}

Here’s what we got:

  • rank state
  • Helpers like setGold, setSilver and setBronze to update state.
  • A computed property isGold we can use in templates.

Player store

// 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());
    }
  }))
);

Our player now has:

  • name,
  • role,
  • and thanks to withRank → also a rank.

Team store

// 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());
    }
  }))
);

The team has:

  • teamName,
  • wins,
  • and again, a rank.

Using the PlayerStore in a component

// 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>Rank: <strong>{{ playerStore.rank() }}</strong></p>

      @if (playerStore.isGold()) {
        <p>🏅 This player is Gold!</p>
      }

      <button (click)="playerStore.promoteToGold()">Promote to Gold</button>
    </div>
  `,
})
export class PlayerCardComponent {
  playerStore = inject(PlayerStore);
}

Using the TeamStore in a component

// 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>Wins: {{ teamStore.wins() }}</p>
      <p>Rank: <strong>{{ teamStore.rank() }}</strong></p>

      @if (teamStore.isGold()) {
        <p>🏆 This team is Gold!</p>
      }

      <button (click)="teamStore.promoteTeamToGold()">Promote Team to Gold</button>
    </div>
  `,
})
export class TeamCardComponent {
  teamStore = inject(TeamStore);
}

Here, we inject our PlayerStore or TeamStore, display the relevant info, and allow a one-click promotion to gold (if only ranking up in games was this easy 😂).

What did we gain?

By extracting rank into a custom feature, we avoided duplicating logic across multiple stores. Both players and teams share the same feature, and we can extend it in the future if needed.

Think of custom store features like power-ups in a game, you create them once and then use them wherever you need that extra boost.

✅ Cleaner code
✅ Reusable state & methods
✅ Less duplication

And the best part: your stores stay lean and focused on their specific responsibilities.


This content originally appeared on DEV Community and was authored by Duško Perić


Print Share Comment Cite Upload Translate Updates
APA

Duško Perić | Sciencx (2025-09-08T08:10:02+00:00) Custom Store Features in NgRx Signal Store. Retrieved from https://www.scien.cx/2025/09/08/custom-store-features-in-ngrx-signal-store/

MLA
" » Custom Store Features in NgRx Signal Store." Duško Perić | Sciencx - Monday September 8, 2025, https://www.scien.cx/2025/09/08/custom-store-features-in-ngrx-signal-store/
HARVARD
Duško Perić | Sciencx Monday September 8, 2025 » Custom Store Features in NgRx Signal Store., viewed ,<https://www.scien.cx/2025/09/08/custom-store-features-in-ngrx-signal-store/>
VANCOUVER
Duško Perić | Sciencx - » Custom Store Features in NgRx Signal Store. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/09/08/custom-store-features-in-ngrx-signal-store/
CHICAGO
" » Custom Store Features in NgRx Signal Store." Duško Perić | Sciencx - Accessed . https://www.scien.cx/2025/09/08/custom-store-features-in-ngrx-signal-store/
IEEE
" » Custom Store Features in NgRx Signal Store." Duško Perić | Sciencx [Online]. Available: https://www.scien.cx/2025/09/08/custom-store-features-in-ngrx-signal-store/. [Accessed: ]
rf:citation
» Custom Store Features in NgRx Signal Store | Duško Perić | Sciencx | https://www.scien.cx/2025/09/08/custom-store-features-in-ngrx-signal-store/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.