Service-based state management helps keep data shared and updated across parts of your app easily. It avoids repeating data and keeps everything in sync.
Service-based state management in Angular
Start learning this pattern below
Jump into concepts and practice - no test required
or
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Introduction
Syntax
Angular
import { Injectable, signal } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class StateService { private _count = signal(0); get count() { return this._count(); } increment() { this._count.update(c => c + 1); } decrement() { this._count.update(c => c - 1); } }
Use @Injectable({ providedIn: 'root' }) to make the service available app-wide.
Use Angular signals to hold and update state reactively.
Examples
Angular
import { Injectable, signal } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class UserService { private _username = signal(''); get username() { return this._username(); } setUsername(name: string) { this._username.set(name); } }
Angular
import { Injectable, signal } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class CartService { private _items = signal<string[]>([]); get items() { return this._items(); } addItem(item: string) { this._items.update(items => [...items, item]); } clear() { this._items.set([]); } }
Sample Program
This component shows a counter and buttons to change it. It uses the shared StateService to keep the count.
Angular
import { Component, inject } from '@angular/core'; import { StateService } from './state.service'; @Component({ selector: 'app-counter', standalone: true, template: ` <h2>Counter: {{ state.count }}</h2> <button (click)="state.increment()">+</button> <button (click)="state.decrement()">-</button> ` }) export class CounterComponent { state = inject(StateService); }
Important Notes
Services are singletons by default, so all components share the same state instance.
Use Angular signals inside services for reactive updates that components can track.
Inject services using inject() or constructor injection in components.
Summary
Service-based state management shares data across components easily.
Angular signals inside services keep state reactive and simple.
Use @Injectable({ providedIn: 'root' }) to make services app-wide singletons.
Practice
1. What is the main benefit of using a service for state management in Angular?
easy
Solution
Step 1: Understand service role in Angular
Services hold data and logic separate from components.Step 2: Recognize state sharing benefit
Services can be injected into many components, sharing the same state instance.Final Answer:
It allows sharing state easily across multiple components. -> Option AQuick Check:
Service-based state management = shared state [OK]
Hint: Services share data across components easily [OK]
Common Mistakes:
- Thinking services replace components
- Believing services auto-update UI without code
- Assuming services speed up app by skipping detection
2. Which decorator and property make an Angular service a singleton across the app?
easy
Solution
Step 1: Identify Angular service decorator
@Injectable marks a class as a service for dependency injection.Step 2: Understand providedIn property
Setting providedIn: 'root' makes the service a singleton app-wide.Final Answer:
@Injectable({ providedIn: 'root' }) -> Option DQuick Check:
Singleton service = @Injectable with providedIn root [OK]
Hint: Use @Injectable({ providedIn: 'root' }) for singleton services [OK]
Common Mistakes:
- Confusing @Component with service decorator
- Using @NgModule providers without providedIn
- Mistaking @Directive for service declaration
3. Given this service code, what will the console log after calling
increment() twice?import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class CounterService {
count = signal(0);
increment() {
this.count.update(c => c + 1);
}
}
const service = new CounterService();
service.increment();
service.increment();
console.log(service.count());medium
Solution
Step 1: Understand initial signal value
The signal count starts at 0.Step 2: Apply two increments
Each increment adds 1, so after two calls, count is 2.Final Answer:
2 -> Option CQuick Check:
0 + 1 + 1 = 2 [OK]
Hint: Each update adds 1; two calls add 2 total [OK]
Common Mistakes:
- Forgetting to call the signal as a function to get value
- Assuming count resets after each increment
- Confusing update with set method
4. What is wrong with this Angular service code for state management?
import { Injectable, signal } from '@angular/core';
@Injectable()
export class DataService {
data = signal([]);
addItem(item: string) {
this.data().push(item);
}
}medium
Solution
Step 1: Check signal mutation method
The code calls this.data() to get the array, then pushes directly.Step 2: Understand signal immutability
Directly mutating the array breaks Angular's reactivity; must use update() or set() to replace value.Final Answer:
The signal value is mutated directly, which breaks reactivity. -> Option BQuick Check:
Mutate signal value immutably to keep reactivity [OK]
Hint: Never mutate signal value directly; use update or set [OK]
Common Mistakes:
- Ignoring providedIn for singleton scope
- Expecting addItem to return value
- Thinking null is better initial value than []
5. You want to share a list of tasks across components using a service with Angular signals. Which approach correctly updates the tasks list immutably when adding a new task?
import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class TaskService {
tasks = signal([]);
addTask(newTask: string) {
// Which line correctly updates tasks?
}
}hard
Solution
Step 1: Understand immutable update with signals
Signals require replacing the value immutably to trigger updates.Step 2: Analyze options for correct update
this.tasks.set([...this.tasks(), newTask]); uses set() with a new array including the new task, which is correct.Final Answer:
this.tasks.set([...this.tasks(), newTask]); -> Option AQuick Check:
Immutable update with set() = correct pattern [OK]
Hint: Use set() with new array copy to update signals immutably [OK]
Common Mistakes:
- Mutating array inside update without returning new array
- Directly pushing to signal value
- Reassigning signal variable instead of updating value
