0Pricing
Angular Academy · Lesson

Signal Stores in Services

Hold app state in signal-based services.

Signal Stores in Services is a free Angular Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Angular Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Signals as State

Angular signals are reactive values that notify consumers when they change. A signal holds a value and re-runs any computation or template that reads it.

Storing signals inside an @Injectable service lets multiple components share the same reactive state.

A Minimal Signal Store

Create a service and hold the state in a signal(). Keep the writable signal private and expose a read-only view.

import { Injectable, signal } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CounterStore {
  private _count = signal(0);
  readonly count = this._count.asReadonly();

  increment() { this._count.update(c => c + 1); }
}

Why providedIn root

providedIn: 'root' makes the service a singleton across the whole app. Every component that injects it shares the exact same signal instance, so state stays in sync.

Reading the Signal

Call the signal like a function to read its value. Inside a template Angular tracks the read and updates the DOM automatically when the value changes.

import { Component, inject } from '@angular/core';
import { CounterStore } from './counter.store';

@Component({
  selector: 'app-counter',
  template: '<button (click)="store.increment()">{{ store.count() }}</button>'
})
export class CounterComponent {
  store = inject(CounterStore);
}

asReadonly Protects State

asReadonly() returns a signal that can be read but not written. Components cannot accidentally call set() or update() on it, so all mutations flow through the store methods.

set vs update

Use set(value) when you have the next value directly. Use update(fn) when the next value depends on the current one.

private _user = signal<User | null>(null);

setUser(u: User) { this._user.set(u); }
clearUser() { this._user.set(null); }
touch() { this._user.update(u => u ? { ...u, seen: true } : u); }

Multiple Signals Per Store

A store can hold several signals representing different slices of state. Keep each writable signal private and expose read-only counterparts.

private _items = signal<Item[]>([]);
private _loading = signal(false);

readonly items = this._items.asReadonly();
readonly loading = this._loading.asReadonly();

Methods Encapsulate Logic

Business rules live in the store methods, not in components. Components call intent-revealing methods like addToCart(item) and the store decides how state changes.

addToCart(item: Item) {
  this._items.update(list => [...list, item]);
}
removeFromCart(id: string) {
  this._items.update(list => list.filter(i => i.id !== id));
}

No Subscriptions Needed

Unlike RxJS-based services, signal stores need no subscribe() and no manual unsubscription. Reading a signal in a template wires up reactivity, and Angular cleans it up when the component is destroyed.

Injecting With inject()

The inject() function works in constructors, field initializers, and factory functions. It is the modern way to grab the store instance.

export class CartComponent {
  private store = inject(CartStore);
  items = this.store.items; // read-only signal
}

Testing a Signal Store

Signal stores are plain classes, so tests are simple: create an instance, call a method, and read the signal value synchronously. No async plumbing required.

it('increments', () => {
  const store = new CounterStore();
  store.increment();
  expect(store.count()).toBe(1);
});

Quick Check

Test your understanding of signal stores.

Recap

You learned to build a signal-based store: keep writable signals private, expose asReadonly() views, mutate via set/update inside methods, and inject the singleton with inject(). No subscriptions, easy testing, shared reactive state.

Frequently asked questions

Is the “Signal Stores in Services” lesson free?

Yes — the full text of “Signal Stores in Services” is free to read here on the web, and the Angular Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Angular Academy course, upgrade to CoddyKit PRO.

What will I learn in “Signal Stores in Services”?

Hold app state in signal-based services. You practise Angular Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Angular Academy?

No prior experience is required. Angular Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Signal Stores in Services” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Angular Academy lesson?

Yes. Every Angular Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Signal Stores in Services
  2. Deriving State with computed
  3. Updating State Immutably
  4. Connecting Signals and RxJS
← Back to Angular Academy