Angular Academy · Lesson

Signal Stores in Services

Hold app state in signal-based services.

Lesson 1 of 413 steps

Signal Stores in Services is a free Angular Academy lesson on CoddyKit. This is lesson 1 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Angular Academy learning path, and your progress syncs across the web and the CoddyKit app. The Angular Academy course includes 4 lessons in total.

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.

Free to start

Learn HTML with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
38
Lessons
144

Frequently Asked Questions

Is the “Signal Stores in Services” lesson free?

Yes — any 3 lessons of the Angular Academy learning path, including “Signal Stores in Services”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Angular Academy course includes 4 lessons in total.

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, so you can start here or from the beginning and move at your own pace. This is lesson 1 of 4.

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