Using signals with services

Now that you've learned two-way binding with model signals, let's explore how to use signals with Angular services. Services are perfect for sharing reactive state across multiple components, and signals make this even more powerful by providing automatic change detection and clean reactive patterns.

In this activity, you'll learn how to create a cart store with signals that allow the cart display component to react to state changes automatically.


  1. Add cart store signals

    Add readonly and computed signals to make the cart state reactive in cart-store.ts.

    // Add the computed importimport {Injectable, signal, computed} from '@angular/core';// Then add these signals to the class:// Readonly signalsreadonly cartItems = this.items.asReadonly();// Computed signalsreadonly totalQuantity = computed(() => {  return this.items().reduce((sum, item) => sum + item.quantity, 0);});readonly totalPrice = computed(() => {  return this.items().reduce((sum, item) => sum + item.price * item.quantity, 0);});

    These signals allow components to reactively access cart data and computed totals. The asReadonly() method prevents external code from modifying the cart items directly, while computed() creates derived state that automatically updates when the source signal changes.

  2. Complete the quantity update methods

    The cart display component in cart-display.ts already uses the cart store signals in its template. Complete the quantity update methods to modify cart items:

    increaseQuantity(id: string) {  const items = this.cartStore.cartItems();  const currentItem = items.find((item) => item.id === id);  if (currentItem) {    this.cartStore.updateQuantity(id, currentItem.quantity + 1);  }}decreaseQuantity(id: string) {  const items = this.cartStore.cartItems();  const currentItem = items.find((item) => item.id === id);  if (currentItem && currentItem.quantity > 1) {    this.cartStore.updateQuantity(id, currentItem.quantity - 1);  }}

    These methods read the current cart state using cartItems() and update quantities through the store's methods. The UI automatically updates when the signals change!

  3. Update the main app component

    Update the main app component in app.ts to use the cart service and display the cart component.

    import {Component, inject} from '@angular/core';import {CartStore} from './cart-store';import {CartDisplay} from './cart-display';@Component({  selector: 'app-root',  imports: [CartDisplay],  template: `    <div class="shopping-app">      <header>        <h1>Signals with Services Demo</h1>        <div class="cart-badge">          Cart: {{ cartStore.totalQuantity() }} items (\${{ cartStore.totalPrice() }})        </div>      </header>      <main>        <cart-display />      </main>    </div>  `,  styleUrl: './app.css',})export class App {  cartStore = inject(CartStore);}

Excellent! You've now learned how to use signals with services. Key concepts to remember:

  • Service-level signals: Services can use signals to manage reactive state
  • Dependency injection: Use inject() to access services with signals in components
  • Computed signals in services: Create derived state that updates automatically
  • Readonly signals: Expose read-only versions of signals to prevent external mutations

In the next lesson, you'll learn about how to use signals with directives!