← Back to Articles         
Angular State Management: Services, Signals & RxJS

Angular State Management: Services, Signals & RxJS

Published on 01 Oct 2026     11 min read Angular
State Management

Angular State Management: Services, Signals & RxJS

These three concepts are closely connected:

Angular Service
      │
      ├── Signals → modern synchronous reactive state
      │
      └── RxJS    → Observable-based reactive state

A service provides the shared location, while Signals or RxJS provide the reactive mechanism for storing and notifying consumers about state changes.


1. How Do You Share State Using Angular Services?

What is Shared State?

When multiple components need the same data, we can move that data from an individual component into a service.

For example:

Header
Product List
Shopping Cart

     ↓

All need Cart information

Instead of maintaining separate copies:

Header        → cartCount
Cart          → cartCount
Checkout      → cartCount

we maintain one shared source:

              CartService
              Shared State
             /     |      \
            /      |       \
        Header    Cart    Checkout

Full Simple Program

cart.service.ts

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

@Injectable({
  providedIn: 'root'
})
export class CartService {

  cartCount = 0;

  addItem(): void {
    this.cartCount++;
  }

  removeItem(): void {
    if (this.cartCount > 0) {
      this.cartCount--;
    }
  }
}

header.ts

import { Component, inject } from '@angular/core';
import { CartService } from './cart.service';

@Component({
  selector: 'app-header',
  standalone: true,
  template: `
    <h2>
      Cart: {{ cartService.cartCount }}
    </h2>
  `
})
export class Header {

  cartService = inject(CartService);
}

product.ts

import { Component, inject } from '@angular/core';
import { CartService } from './cart.service';

@Component({
  selector: 'app-product',
  standalone: true,
  template: `
    <h2>Products</h2>

    <button (click)="addToCart()">
      Add to Cart
    </button>
  `
})
export class Product {

  private cartService = inject(CartService);

  addToCart(): void {
    this.cartService.addItem();
  }
}

app.ts

import { Component } from '@angular/core';
import { Header } from './header';
import { Product } from './product';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    Header,
    Product
  ],
  template: `
    <app-header />

    <app-product />
  `
})
export class App {
}

How Does It Work?

Because the service has:

providedIn: 'root'

Angular normally creates one root-level instance:

              CartService
              cartCount
                  │
          ┌───────┴───────┐
          ↓               ↓
       Header           Product
          ↑               │
          │               │
          └──── Same ─────┘
              instance

Product calls:

this.cartService.addItem();

which changes:

cartCount

0 → 1

Header accesses the same service instance.

Important limitation

Although the simple property demonstrates service sharing, for reactive shared state, Signals or RxJS are generally preferable.

That brings us to the next two approaches.


2. How Do You Manage State Using Signals?

What is Signal-Based State Management?

Angular Signals provide a reactive way of storing state.

A signal:

  1. Stores a value.
  2. Allows us to update the value.
  3. Notifies Angular consumers when the value changes.

For modern Angular, a service containing signals is a clean approach for many shared-state requirements.


Full Signal State Example

Let's create a shopping cart.

cart.service.ts

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

@Injectable({
  providedIn: 'root'
})
export class CartService {

  private readonly _cartCount = signal(0);

  readonly cartCount =
    this._cartCount.asReadonly();

  readonly hasItems = computed(
    () => this._cartCount() > 0
  );

  addItem(): void {
    this._cartCount.update(
      count => count + 1
    );
  }

  removeItem(): void {
    this._cartCount.update(
      count => count > 0
        ? count - 1
        : 0
    );
  }

  clearCart(): void {
    this._cartCount.set(0);
  }
}

header.ts

import {
  Component,
  inject
} from '@angular/core';

import { CartService } from './cart.service';

@Component({
  selector: 'app-header',
  standalone: true,

  template: `
    <h2>
      Cart: {{ cartService.cartCount() }}
    </h2>

    @if (cartService.hasItems()) {
      <p>Items available in cart</p>
    }
  `
})
export class Header {

  readonly cartService =
    inject(CartService);
}

product.ts

import {
  Component,
  inject
} from '@angular/core';

import { CartService } from './cart.service';

@Component({
  selector: 'app-product',
  standalone: true,

  template: `
    <h2>Products</h2>

    <button (click)="addToCart()">
      Add
    </button>

    <button (click)="removeFromCart()">
      Remove
    </button>
  `
})
export class Product {

  private readonly cartService =
    inject(CartService);

  addToCart(): void {
    this.cartService.addItem();
  }

  removeFromCart(): void {
    this.cartService.removeItem();
  }
}

app.ts

import { Component } from '@angular/core';

import { Header } from './header';
import { Product } from './product';

@Component({
  selector: 'app-root',
  standalone: true,

  imports: [
    Header,
    Product
  ],

  template: `
    <app-header />

    <app-product />
  `
})
export class App {
}

Signal Flow

Initial state:

_cartCount
    ↓
    0

Product calls:

this.cartService.addItem();

Service executes:

this._cartCount.update(
  count => count + 1
);

Now:

0
↓
1

Signal notifies its consumers:

Product
   │
   │ addItem()
   ▼
CartService
   │
   │ _cartCount
   │ 0 → 1
   ▼
Signal changed
   │
   ▼
Header
   │
   ▼
Cart: 1

Why Use a Private Writable Signal?

Notice:

private readonly _cartCount =
  signal(0);

The writable signal is private.

Then we expose:

readonly cartCount =
  this._cartCount.asReadonly();

Components can read:

cartService.cartCount()

but cannot directly do:

cartService.cartCount.set(100);

Instead, they must call:

cartService.addItem();

This gives the service control over state changes.

This pattern is very useful:

Component
    │
    │ addItem()
    ▼
Service
    │
    ▼
Private writable state
    │
    ▼
Public readonly state

Using computed()

Suppose we want to know whether the cart has items.

Instead of maintaining another independent state variable:

hasItems = true;

derive it:

readonly hasItems = computed(
  () => this._cartCount() > 0
);

Now:

cartCount = 0
     ↓
hasItems = false


cartCount = 3
     ↓
hasItems = true

This prevents duplicated state.


3. How Do You Manage State Using RxJS?

Before Signals, Angular applications commonly used RxJS extensively for shared reactive state, and RxJS remains important—especially for asynchronous streams and event composition.

A common pattern is:

Service
   ↓
BehaviorSubject
   ↓
Observable
   ↓
Components

Full RxJS State Example

cart.service.ts

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

import {
  BehaviorSubject,
  Observable
} from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class CartService {

  private readonly cartCountSubject =
    new BehaviorSubject<number>(0);

  readonly cartCount$: Observable<number> =
    this.cartCountSubject.asObservable();

  addItem(): void {

    const currentCount =
      this.cartCountSubject.value;

    this.cartCountSubject.next(
      currentCount + 1
    );
  }

  removeItem(): void {

    const currentCount =
      this.cartCountSubject.value;

    if (currentCount > 0) {

      this.cartCountSubject.next(
        currentCount - 1
      );

    }
  }

  clearCart(): void {
    this.cartCountSubject.next(0);
  }
}

header.ts

import {
  Component,
  inject
} from '@angular/core';

import { AsyncPipe } from '@angular/common';

import { CartService } from './cart.service';

@Component({
  selector: 'app-header',
  standalone: true,

  imports: [
    AsyncPipe
  ],

  template: `
    <h2>
      Cart:
      {{ cartService.cartCount$ | async }}
    </h2>
  `
})
export class Header {

  readonly cartService =
    inject(CartService);
}

product.ts

import {
  Component,
  inject
} from '@angular/core';

import { CartService } from './cart.service';

@Component({
  selector: 'app-product',
  standalone: true,

  template: `
    <h2>Products</h2>

    <button (click)="addToCart()">
      Add
    </button>

    <button (click)="removeFromCart()">
      Remove
    </button>
  `
})
export class Product {

  private readonly cartService =
    inject(CartService);

  addToCart(): void {
    this.cartService.addItem();
  }

  removeFromCart(): void {
    this.cartService.removeItem();
  }
}

app.ts

import { Component } from '@angular/core';

import { Header } from './header';
import { Product } from './product';

@Component({
  selector: 'app-root',
  standalone: true,

  imports: [
    Header,
    Product
  ],

  template: `
    <app-header />

    <app-product />
  `
})
export class App {
}

Understanding BehaviorSubject

We create:

private readonly cartCountSubject =
  new BehaviorSubject<number>(0);

0 is the initial state.

BehaviorSubject
      ↓
Initial value
      ↓
0

When an item is added:

this.cartCountSubject.next(
  currentCount + 1
);

.next() emits a new value.

0
↓
next(1)
↓
1
↓
Subscribers notified

Why Expose an Observable?

We keep this private:

private readonly cartCountSubject =
  new BehaviorSubject<number>(0);

and expose:

readonly cartCount$ =
  this.cartCountSubject.asObservable();

This prevents components from doing:

cartCountSubject.next(500);

Components can observe:

cartCount$

but state modifications remain controlled by:

CartService

So:

                 CartService
                     │
          ┌──────────┴──────────┐
          ↓                     ↓
Private BehaviorSubject    Public Observable
     Writable                 Read-only
          │                     │
          │                     ▼
          │                 Components
          │
addItem()/removeItem()

Why $ in cartCount$?

The $ is a common RxJS naming convention indicating:

cartCount$

is an:

Observable

For example:

products$
user$
cartItems$
loading$

The $ is a convention, not a TypeScript requirement.


Using AsyncPipe

The template uses:

{{ cartService.cartCount$ | async }}

AsyncPipe subscribes to the Observable and provides the latest emitted value.

Conceptually:

BehaviorSubject
      ↓
cartCount$
      ↓
AsyncPipe
      ↓
Template

It also manages its subscription lifecycle automatically.


Signals vs RxJS for State Management

This is the important interview comparison.

Signals RxJS
signal() BehaviorSubject
Read using count() Observe via count$
set() next()
update() next() with derived value
computed() map(), combineLatest()
Synchronous state model Stream/event model
Simple for UI state Powerful for async streams
Built into Angular reactive primitives RxJS library integrated heavily with Angular
No subscription for basic reads Observable consumption involves subscription/AsyncPipe

Signal Example

private count =
  signal(0);

count.update(
  value => value + 1
);

Flow:

Signal
  ↓
Current value
  ↓
Update
  ↓
UI reacts

RxJS Example

private countSubject =
  new BehaviorSubject(0);

countSubject.next(1);

Flow:

BehaviorSubject
      ↓
Emits value
      ↓
Observable
      ↓
Subscriber
      ↓
UI reacts

Which One Should You Use?

For modern Angular, Signals are often a natural choice for synchronous application/UI state.

Examples:

Current user state
Selected product
Cart count
Sidebar state
Loading flag
Theme
Filters

RxJS is especially useful when you're dealing with asynchronous streams and event composition.

Examples:

HTTP requests
Search input streams
WebSocket events
Route streams
Debouncing
Cancellation
Combining multiple async sources

For example:

searchText$
  .pipe(
    debounceTime(300),
    distinctUntilChanged(),
    switchMap(text =>
      this.productService.search(text)
    )
  );

RxJS is excellent here because the problem is fundamentally about an asynchronous stream.


Can Signals and RxJS Be Used Together?

Yes.

This is common in modern Angular.

For example:

ASP.NET Core API
       ↓
HttpClient
       ↓
Observable
       ↓
Service
       ↓
Signal State
       ↓
Components

You don't need to choose one technology for the entire application.

A practical architecture can use:

RxJS
  ↓
Async operations / event streams

Signals
  ↓
Application/UI state

Services
  ↓
State ownership and business operations

Very Important: Service Is Not an Alternative to Signals/RxJS

This is a common interview misunderstanding.

These are not three mutually exclusive choices:

Service
vs
Signals
vs
RxJS

Instead:

              Angular Service
                    │
            owns shared state
                    │
          ┌─────────┴─────────┐
          ↓                   ↓
       Signals              RxJS
   reactive state      reactive streams

For example:

@Injectable({
  providedIn: 'root'
})
export class CartStore {

  private readonly _items =
    signal<Product[]>([]);

}

Here:

  • CartStore is an Angular service.
  • _items is a Signal.
  • The service provides the shared state.

Similarly:

@Injectable({
  providedIn: 'root'
})
export class CartStore {

  private readonly itemsSubject =
    new BehaviorSubject<Product[]>([]);

}

Here:

  • CartStore is an Angular service.
  • BehaviorSubject is RxJS.
  • The service owns the shared state.

Simple Decision Guide

Do I need state only inside one component?
              │
             YES
              ↓
        Local Signal
        signal()


Do multiple components need the state?
              │
             YES
              ↓
      Shared Angular Service
              │
        ┌─────┴─────┐
        ↓           ↓
     Signals       RxJS


Is it mainly synchronous
application/UI state?
        ↓
     Signals


Is it mainly asynchronous
streams/events requiring operators?
        ↓
      RxJS

Key Points

  • Angular services can provide a shared state owner for multiple components.
  • providedIn: 'root' normally gives a root-level singleton service.
  • Signals are well suited to reactive synchronous state.
  • Use signal() to create writable signal state.
  • Use set() to replace a signal value.
  • Use update() to calculate a new value from the current value.
  • Use computed() for derived state.
  • Prefer exposing readonly signals when consumers shouldn't modify state directly.
  • RxJS commonly uses BehaviorSubject for simple shared state with an initial/current value.
  • Use .next() to emit a new RxJS state value.
  • Expose asObservable() instead of exposing the writable subject directly.
  • AsyncPipe is convenient for consuming Observables in templates.
  • Signals and RxJS can coexist.
  • Services, Signals, and RxJS are complementary rather than mutually exclusive.

Interview Questions & Answers

1. How do you share state between Angular components?

A common approach is to move the state into a root-provided Angular service and inject that service into the components that need it.

@Injectable({
  providedIn: 'root'
})
export class CartService {
}

2. How do you manage state using Signals?

Create state with signal(), modify it using set() or update(), and optionally expose it as readonly.

private count = signal(0);

count.update(x => x + 1);

3. How do you manage shared state with RxJS?

A common pattern uses a private BehaviorSubject and exposes it as an Observable.

private countSubject =
  new BehaviorSubject(0);

count$ =
  this.countSubject.asObservable();

Update it using:

this.countSubject.next(10);

4. Why use BehaviorSubject for state?

Because it stores a current value, requires an initial value, and immediately provides the latest value to new subscribers.


5. Why make the Signal or BehaviorSubject private?

To prevent components from modifying shared state directly.

Component
    ↓
Service method
    ↓
State mutation

This provides controlled state changes.


6. Signals or RxJS—which should I use?

Signals are often simpler for synchronous UI/application state. RxJS is particularly useful for asynchronous streams, cancellation, debouncing, event composition, and complex stream transformations.


7. Can Signals and RxJS be used together?

Yes. A common Angular application uses RxJS for HTTP/event streams and Signals for application/UI state.


Interview Quick Answer

Shared Angular state is commonly placed in a root-provided service. In modern Angular, the service can use Signals (signal, computed, set, update) for synchronous reactive state. RxJS can use a private BehaviorSubject exposed as an Observable, with .next() used to update the state. Signals are often simpler for UI state, while RxJS is especially powerful for asynchronous streams and event composition.

← Back to Articles