Standalone Components and Signals

Chapter 13
Standalone Components and Signals
Where Angular is heading — and a reactive primitive that feels a lot like useState

This whole course has quietly used the modern direction already — every component has been standalone: true, and Chapters 10–11 used provideRouter/provideHttpClient rather than the older module setup. This chapter names those choices explicitly, then introduces signals — Angular's newer reactivity primitive, the closest thing in Angular to React's useState.

Standalone Components, Recapped

Historically, Angular grouped components into NgModules — separate files declaring which components, directives, and pipes belonged together and what they could use. Standalone components (now the default) drop that layer: each component declares its own imports directly, as seen in every example so far. The result is less boilerplate, no app.module.ts to maintain, and an import list that lives right next to the component using it.

You will still encounter NgModule code
Plenty of existing Angular apps and tutorials predate standalone components and are built around @NgModule with a declarations array and a root AppModule. It's fully supported and not going away soon, but new projects should use standalone (the ng new default). Recognize the module-based structure when reading older code; this course deliberately teaches only the standalone approach.

Signals — A Reactive Value

import { Component, signal } from '@angular/core'; @Component({ selector: 'app-counter', standalone: true, template: ` <p>Count: {{ count() }}</p> <button (click)="increment()">+1</button> `, }) export class CounterComponent { count = signal(0); increment() { this.count.update((n) => n + 1); } }

signal(0) creates a reactive value holding 0. Reading it is a function callcount(), both in the template and in code. Writing is done with .set(value) (a new value) or .update(fn) (based on the current one) — directly parallel to React's setCount(5) and setCount(n => n + 1). When a signal changes, Angular knows precisely which parts of the template depend on it and updates only those, more surgically than the older change-detection mechanism.

SignalReact useState equivalent
count = signal(0)const [count, setCount] = useState(0)
count()count (read)
count.set(5)setCount(5)
count.update(n => n + 1)setCount(n => n + 1)

computed — Derived Signals

import { signal, computed } from '@angular/core'; price = signal(100); quantity = signal(2); total = computed(() => this.price() * this.quantity()); // total() is 200, and recalculates automatically whenever price or quantity changes

computed derives a new signal from others — its value automatically recalculates whenever any signal it reads changes, and (importantly) is cached until then, so it only recomputes when genuinely needed. This is the signals version of React's useMemo (Intermediate Chapter 7), but the dependency tracking is automatic: there's no dependency array to maintain — Angular knows total depends on price and quantity simply because it read them.

effect — Reacting to Signal Changes

import { effect } from '@angular/core'; constructor() { effect(() => { console.log('count is now', this.count()); }); }

effect runs a side effect whenever any signal it reads changes — automatically tracked, the same way computed works. It's the rough equivalent of React's useEffect with the relevant value in its dependency array, again with no manual dependency list. Effects are for genuine side effects (logging, syncing to localStorage); deriving a value should use computed instead.

Why Signals Matter

Signals are Angular's strategic direction for reactivity — gradually offering a simpler, more explicit, and more performant alternative to the change-detection-plus-RxJS model that came before. They don't replace RxJS (Observables are still the right tool for streams of events and HTTP, Chapter 11), but for component-local state, signals are increasingly the recommended default. Inputs can even be signals now (input() instead of @Input()), and the async pipe has a signal counterpart in toSignal() — the ecosystem is steadily building around them.

Coming from React, signals will feel immediately familiar
Of everything in Angular, signals map most cleanly onto React knowledge: signal/set/update is useState, computed is useMemo, effect is useEffect — all with automatic dependency tracking instead of manual arrays. If the rest of Angular felt like a different paradigm, this is the chapter where it converges back toward familiar ground.
Angular signalsReact hooks
signal()useState
computed()useMemo (auto-tracked)
effect()useEffect (auto-tracked)

Coding Challenges

Challenge 1

Build a counter using a signal, with +1/-1/reset buttons calling set/update, reading the value with count() in the template.

📄 View solution
Challenge 2

Build a component with price and quantity signals and a computed total signal, plus inputs/buttons to change price and quantity, confirming total updates automatically with no manual recalculation.

📄 View solution
Challenge 3

Build a component with a signal whose value is persisted to localStorage via an effect (saving on every change) and read back as the initial value, so it survives a page refresh — the signals version of the React useLocalStorage hook.

📄 View solution

Chapter 13 Quick Reference

  • Standalone components (the default) replace NgModules; each declares its own imports
  • signal(value) — a reactive value; read with x(), write with .set() / .update() (= useState)
  • computed(() => ...) — a derived signal, auto-recalculated and cached (= useMemo, no dep array)
  • effect(() => ...) — runs a side effect when read signals change (= useEffect, auto-tracked)
  • Signals are Angular's strategic direction for reactivity — preferred for component-local state
  • Signals complement, don't replace, RxJS — Observables still own event streams and HTTP
  • Next chapter: testing — Jasmine, Karma, and TestBed (the final chapter)