# Angular from Zero - The Full-Framework Deal

> What Angular actually is - a complete, TypeScript-first application framework - explained through its modern core: standalone components, signals, dependency injection, and just enough RxJS.


---

# Angular from Zero - The Full-Framework Deal

Angular's reputation runs a decade behind its reality. The framework people warn you about -
NgModules everywhere, boilerplate for a hello world, RxJS required to display a number - is the
old Angular. The current one boots from a single standalone component, manages state with signals
that look a lot like Vue and Svelte's reactivity, and writes conditionals as readable `@if` blocks.
It's still the most opinionated of the big frameworks - that's its actual identity, not a flaw:
**Angular is the one that ships the whole application architecture in the box**, TypeScript-first,
with a CLI that generates the pieces.

This guide teaches that modern core - and, because you *will* inherit older Angular at work, flags
the legacy dialect at every point where the two differ.

## How to read this

- **In a panic right now?** Jump to [Phase 7: When Angular Breaks](07-when-it-breaks.md) - the
  cheat-card decodes the NG-numbered errors.
- **Want it to finally make sense?** Read in order. Signals (phase 3) and dependency injection
  (phase 5) are the two load-bearing ideas.

## The phases

1. **[What Angular Actually Is](01-what-angular-actually-is.md)** - a framework, not a library:
   what's in the box and what the CLI does.
2. **[Components and Templates](02-components-and-templates.md)** - `@Component`, bindings with
   `[ ]` and `( )`, and the `@if`/`@for` control flow.
3. **[Signals](03-signals.md)** - `signal`, `computed`, and how modern Angular knows what changed.
4. **[Component Inputs and Outputs](04-inputs-and-outputs.md)** - `input()`, `output()`, and
   `model()` for two-way.
5. **[Services and Dependency Injection](05-services-and-di.md)** - Angular's answer to shared
   state and shared logic.
6. **[HTTP and Just Enough RxJS](06-http-and-just-enough-rxjs.md)** - `HttpClient`, observables
   without drowning, and `toSignal`.
7. **[When Angular Breaks](07-when-it-breaks.md)** - NG-numbered errors, missing imports, and
   signal mistakes, decoded.
8. **[Where to Go Next](08-where-to-go-next.md)** - Router, forms, RxJS depth, and what to defer.

> Deliberately deferred to follow-up guides: the Router in depth, reactive forms, NgRx and signal
> stores, SSR/hydration, testing, and RxJS beyond survival level. Core model first.


---

# What Angular Actually Is

React is a rendering library you assemble a stack around. Vue is a framework with an optional
ecosystem. Angular is the third answer: **the whole application platform, decided for you.**
Router, HTTP client, forms system, dependency injection, testing setup, build pipeline - one
coherent set, one version number, maintained together by one team at Google. You give up choosing;
you get back never having to choose - and a codebase that looks like every other Angular codebase,
which is precisely why large organizations keep picking it.

## The identity, concretely

Three commitments define Angular against its neighbors:

- **TypeScript is not optional.** Angular is written in TypeScript and assumes you are too. Types
  aren't a bonus layer; the framework's APIs, tooling, and error messages lean on them. If your
  TypeScript is shaky, budget for leveling it up alongside this guide - it pays either way.
- **The architecture ships in the box.** Where a React team debates data-fetching libraries and a
  Vue team decides when to adopt Pinia, an Angular team uses `HttpClient` and services, because
  that's what's there. Fewer decisions, more uniformity, at the cost of flexibility at the edges.
- **The CLI is the workflow.** You don't hand-create files; you generate them, and the generated
  code follows the conventions the rest of the toolchain expects.

```console
$ npm install -g @angular/cli
$ ng new my-shop
? Which stylesheet format would you like to use? CSS
? Do you want to enable Server-Side Rendering (SSR)? No

$ cd my-shop && ng serve

Initial chunk files | Names   | Raw size
main.js             | main    | 95.21 kB
Application bundle generation complete.
  ➜  Local:   http://localhost:4200/
```

*What just happened:* the CLI scaffolded a project - TypeScript configured, build pipeline wired,
test runner ready - and started the dev server. Day to day you'll also lean on
`ng generate component product-card` (files plus boilerplate, conventions included) and
`ng build` for production. The CLI even automates version upgrades (`ng update`), which is less
glamorous and more valuable than it sounds three years into an app's life.

## The unit: a standalone component

Everything on screen is a component - a TypeScript class with a decorator:

```ts
// src/app/app.ts
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>{{ title() }}</h1>
    <button (click)="rename()">Rename</button>
  `,
})
export class App {
  title = signal('My Shop');
  rename() {
    this.title.set('My Better Shop');
  }
}
```

*What just happened:* the `@Component` decorator attaches metadata to a class - the `selector` is
the HTML tag this component answers to (`<app-root>` in `index.html`), and the `template` is its
markup (inline here; larger components point at a separate `templateUrl` file). The class holds
state and methods; the template reads them. `signal(...)` is phase 3's whole topic - for now, note
that state lives in signals and templates call them like functions: `{{ title() }}`.

📝 **Terminology:** components like this are **standalone** - they declare what they need and can
be used directly. Older Angular grouped components into **NgModules** (`@NgModule` declarations
in `app.module.ts` files), and most tutorials older than a couple of years - plus most codebases
you'll inherit - are built that way. The concepts in this guide transfer; the packaging differs.
If a tutorial starts with `app.module.ts`, it's teaching the legacy structure. New Angular code is
standalone by default, and this guide is standalone throughout.

## How the pieces will fit

The box is big; the load-bearing walls are few. This guide's map:

```mermaid
flowchart TD
  C[Components + templates - ph.2] --> S[Signals: state - ph.3]
  C --> IO[Inputs/outputs - ph.4]
  C --> D[Services via DI - ph.5]
  D --> H[HttpClient + RxJS - ph.6]
```

Components render. Signals tell them what changed. Inputs and outputs wire components to each
other. Services - delivered by dependency injection - hold what components share: state, logic,
and the HTTP layer. Those five ideas *are* daily Angular; the rest of the box (router, forms,
animations) attaches to them.

## An even-handed word on choosing it

| You are... | Angular's deal |
|---|---|
| A large team wanting uniform codebases and long-term support | ✓ this is the core case - conventions scale across people |
| An enterprise with 5-10 year application lifespans | ✓ Google's LTS cadence and `ng update` are built for this |
| A solo dev or small team optimizing for speed-to-first-feature | The box has a learning tax; React/Vue/Svelte get you moving faster |
| Building a content site where SEO and first paint dominate | Possible (SSR exists) but the meta-frameworks next door are more purpose-built |
| Coming from a strongly-typed backend (Java, C#) | ✓ DI + decorators + classes will feel like home faster than JSX will |

## Recap

1. Angular ships the whole platform - router, HTTP, forms, DI, testing - as one versioned,
   conventional set. Fewer choices, more uniformity.
2. TypeScript is mandatory and load-bearing, not decorative.
3. The CLI generates, builds, serves, and upgrades; it's the workflow, not a convenience.
4. A component = a decorated TypeScript class + template; modern components are standalone, and
   NgModule-based code is the legacy dialect you'll read at work.
5. Daily Angular is five ideas: components, signals, inputs/outputs, services/DI, HttpClient.

```quiz
[
  {
    "q": "A React developer asks what they get for accepting Angular's bigger learning tax. Per this phase, the core answer is:",
    "choices": [
      "Better runtime performance than a library can achieve",
      "The whole application architecture ships in the box, so teams don't assemble or debate a stack - and every Angular codebase looks alike",
      "No build step is required",
      "JavaScript instead of TypeScript"
    ],
    "answer": 1,
    "why": [
      "Performance across the big frameworks is comparable and workload-dependent - the differentiator is architectural, not speed.",
      null,
      "Angular's build pipeline is substantial - the CLI manages it, but it's very much there.",
      "It's the reverse: TypeScript is mandatory."
    ],
    "explain": "Angular's identity is the complete, opinionated platform: router, HTTP, forms, DI, and conventions decided once, uniformly, for every team in the org."
  },
  {
    "q": "A tutorial opens by editing app.module.ts and adding components to a declarations array. What should you conclude?",
    "choices": [
      "It's teaching a different framework",
      "It's teaching the legacy NgModule structure - concepts transfer, but modern code is standalone components",
      "The tutorial is for server-side Angular",
      "declarations is where standalone components are registered"
    ],
    "answer": 1,
    "why": [
      "It's Angular alright - an earlier packaging of it.",
      null,
      "SSR uses the same component model - module-vs-standalone is an era split, not a server split.",
      "Standalone components skip declarations arrays entirely - importing what they need directly."
    ],
    "explain": "NgModules grouped and declared components for a decade, and most existing codebases use them. New Angular defaults to standalone components; read both, write standalone."
  }
]
```


---

# Components and Templates

Angular templates are HTML plus two bracket conventions and a block syntax. The brackets carry the
whole binding model: `[square]` means data flowing *into* a DOM property, `(round)` means events
flowing *out*. Once those two register, every Angular template reads left to right - and the modern
`@if`/`@for` blocks (which replaced the older `*ngIf`/`*ngFor` you'll meet in legacy code) read
like the TypeScript around them.

## Interpolation and property binding: data in

```ts
@Component({
  selector: 'app-product',
  template: `
    <h2>{{ name() }}</h2>
    <img [src]="imageUrl()" [alt]="name()" />
    <button [disabled]="!inStock()">Buy</button>
  `,
})
export class Product {
  name = signal('Kettle');
  imageUrl = signal('/kettle.jpg');
  inStock = signal(true);
}
```

*What just happened:* `{{ expr }}` interpolates into text. `[src]="expr"` binds a DOM *property*
to an expression - re-evaluated whenever the signals it reads change. Without the brackets,
`disabled="false"` would set a plain HTML attribute to the literal string `"false"` - which, for
boolean attributes, means *disabled* (the string is truthy). The brackets are the difference
between "this text" and "this expression, kept live" - the same string-vs-expression rule as every
framework, wearing square brackets.

Class and style get ergonomic forms you'll use daily: `[class.active]="isActive()"` toggles one
class by boolean; `[style.width.px]="size()"` binds one style with units.

## Event binding: actions out

```ts
template: `
  <button (click)="addToCart(product().id)">Add</button>
  <input (input)="onSearch($event)" />
  <form (submit)="save(); $event.preventDefault()">...</form>
`
```

*What just happened:* `(click)="..."` runs the statement when the event fires - a method call,
usually. `$event` is the native event object when you need it. Like Vue's compiled templates (and
unlike JSX), writing the call with parentheses is correct - it runs on the event, not during
render; there's no pass-the-function-don't-call-it trap here.

## Branching: @if

```html
@if (cart().length === 0) {
  <p>Your cart is empty.</p>
} @else if (cart().length < 10) {
  <p>{{ cart().length }} items.</p>
} @else {
  <p>Bulk order!</p>
}
```

Block syntax, braces and all, right in the template. A false branch **unmounts** its contents -
DOM gone, component state inside destroyed - the standard conditional-rendering semantics. For
hide-but-keep-alive, bind the class: `[class.hidden]="!open()"` plus a CSS rule.

📝 **Terminology:** legacy spelling: `<p *ngIf="cart.length === 0">` - a "structural directive"
with its asterisk, plus `ng-template` gymnastics for the else branch. The `@if` block does the
same job with less ceremony; codebases are migrating incrementally, so read both fluently.

## Loops: @for and the mandatory track

```html
<ul>
  @for (todo of todos(); track todo.id) {
    <li>{{ todo.text }}</li>
  } @empty {
    <li>Nothing to do.</li>
  }
</ul>
```

*What just happened:* one `<li>` per item, an `@empty` block for the zero case - and a `track`
expression that is **mandatory**. Angular took the identity lesson every framework teaches
(React's `key`, Vue and Svelte's keyed lists) and made it required syntax: `track todo.id` tells
the renderer how to match items across updates, so reorders move DOM instead of rewriting it and
row state stays with its row.

💡 **Key point:** Angular forcing `track` is the framework encoding a decade of production bugs
into the compiler. You can still write `track $index` - and for lists that reorder or delete,
that's the same corruption-by-position bug as an unkeyed list elsewhere, just explicitly chosen.
Track a stable id whenever the list's shape can change. (Legacy spelling: `*ngFor="let t of
todos; trackBy: myTrackFn"` - where `trackBy` was optional, usually omitted, and its omission was
a notorious performance foot-gun on big lists.)

## Two-way on inputs: banana in a box

```ts
import { FormsModule } from '@angular/forms';

@Component({
  imports: [FormsModule],
  template: `
    <input [(ngModel)]="email" placeholder="you@example.com" />
    <p>Signing up: {{ email }}</p>
  `,
})
export class Signup {
  email = '';
}
```

*What just happened:* `[(ngModel)]` - the community reads the syntax as "banana in a box" - is
two-way binding on a form control: value in (`[ ]`), changes out (`( )`), composed. It requires
importing `FormsModule` into the component (note the `imports` array - standalone components
declare their template dependencies, which phase 7 revisits as a classic error source).
`[(ngModel)]` serves simple forms well; Angular's fuller answer for serious forms - reactive
forms - is deferred to a follow-up guide, and phase 8 places it on the map.

## Recap

1. `{{ expr }}` for text; `[prop]="expr"` for live property binding - bare attributes are just
   strings.
2. `(event)="statement"` runs on the event; `$event` is the native object; calls-with-parens are
   correct here.
3. `@if / @else` unmounts on false; `[class.x]` toggles are the keep-alive alternative.
4. `@for (... ; track item.id)` - track is mandatory, and `track $index` on mutable lists is the
   old corruption bug, opted into.
5. Legacy dialect: `*ngIf`/`*ngFor`/`trackBy` - same semantics, older spelling.
6. `[(ngModel)]` gives two-way form binding; it needs `FormsModule` in the component's imports.

```quiz
[
  {
    "q": "A button written as <button disabled=\"false\"> stays disabled. Why?",
    "choices": [
      "Angular inverts boolean attributes by default",
      "Without brackets it's a plain HTML attribute set to the string \"false\", which is truthy - [disabled]=\"false\" binds the expression",
      "disabled requires FormsModule",
      "The component forgot to import CommonModule"
    ],
    "answer": 1,
    "why": [
      "Angular doesn't touch bare attributes at all - that's the point: without brackets, the framework isn't involved.",
      null,
      "FormsModule powers ngModel, not native attributes.",
      "No module fixes a binding that was never a binding."
    ],
    "explain": "Square brackets mean 'evaluate this expression and bind the DOM property.' Without them you wrote static HTML, and any non-empty string on a boolean attribute means true."
  },
  {
    "q": "Why did Angular make the track expression in @for mandatory rather than optional like the old trackBy?",
    "choices": [
      "To make templates more verbose for readability",
      "Item identity is what lets the renderer move DOM instead of rewriting it - optional trackBy was omitted so often it became a chronic performance and state bug",
      "track is needed for TypeScript type inference",
      "It replaces the need for @empty blocks"
    ],
    "answer": 1,
    "why": [
      "Verbosity is a cost here, paid deliberately for correctness.",
      null,
      "Types flow from the iterated array either way.",
      "@empty handles the zero case - unrelated to identity."
    ],
    "explain": "Every framework needs list identity (React keys, Vue/Svelte keyed each). Angular's old optional trackBy was chronically skipped, so the modern syntax makes the identity decision explicit and required."
  },
  {
    "q": "In an Angular template, (click)=\"remove(item.id)\" - does the call run during rendering, like it would in JSX?",
    "choices": [
      "Yes - wrap it in an arrow function to be safe",
      "No - templates are compiled, and the statement runs only when the event fires",
      "Only if the method returns void",
      "It runs once at render and once per click"
    ],
    "answer": 1,
    "why": [
      "Arrow-wrapping is a JSX necessity; here it's just extra characters.",
      null,
      "Return types don't change when template statements execute.",
      "Nothing executes at render - the compiler wires the statement to the event."
    ],
    "explain": "Angular templates (like Vue's) are compiled: the (event) binding is wiring, not evaluation. The call-during-render trap belongs to JSX, where braces hold live JavaScript."
  }
]
```


---

# Signals

Every phase so far has written state as `signal(...)` and read it as `title()`. Time to pay that
off. Signals are Angular's modern reactivity - the answer to "how does the framework know what
changed?" - and they'll feel familiar if you've read our Vue or Svelte guides, because all three
frameworks converged on the same idea: **track reads, notify on writes.** Angular's spelling is
just the most explicit of the family.

## The API: three verbs

```ts
import { Component, signal, computed } from '@angular/core';

@Component({
  selector: 'app-cart',
  template: `
    <p>{{ count() }} items - {{ total() }} cents</p>
    <button (click)="add(1900)">Add kettle</button>
    <button (click)="clear()">Clear</button>
  `,
})
export class Cart {
  items = signal<number[]>([]);

  count = computed(() => this.items().length);
  total = computed(() => this.items().reduce((s, p) => s + p, 0));

  add(price: number) {
    this.items.update(list => [...list, price]);
  }
  clear() {
    this.items.set([]);
  }
}
```

*What just happened,* verb by verb:

- **Read: call it.** `items()` returns the current value - and, when called during rendering,
  registers the caller as a dependent. That call syntax is the visible tip of the whole tracking
  system.
- **Write: `set` or `update`.** `set(newValue)` replaces; `update(fn)` computes the new value from
  the old - the right choice whenever new depends on old (the same old-value discipline as
  React's function-form setters, made a named method).
- **Derive: `computed`.** Reads other signals, caches its result, recomputes only when a
  dependency changed. Chains resolve automatically - `total` depends on `items`, the template
  depends on `total`, and a single `add()` updates exactly that chain.

⚠️ **Gotcha:** `update` hands you the current value, and the habit from other frameworks of
mutating it (`list.push(price); return list`) returns the *same object* - and signals, like
React, use reference equality to decide whether anything changed. Same reference = no
notification = stale screen. Return a fresh array/object (`[...list, price]`), exactly as the
example does. Angular's signals sit on the immutable-update side of the family tree, with Vue and
Svelte's proxies on the other.

## Why calling, not touching

Vue tracks reads through proxy traps (`user.name`), Svelte's compiler rewrites variable access -
both hide the tracking behind normal-looking syntax. Angular chose visibility: a read is a call,
so **every place your code depends on a signal is marked by parentheses.** The cost is typing
`()`; the payoff is that dependency edges are visible in code review, and there's no "is this
variable reactive?" ambiguity - if it's called, it's live; if it's not a signal, it's not.

```mermaid
flowchart LR
  W["items.update(...)"] -->|notifies| C1["count()"]
  W -->|notifies| C2["total()"]
  C2 -->|notifies| T[template rebinds]
  C1 -->|notifies| T
```

📝 **Terminology - the era note.** Before signals, Angular detected changes with **zone.js**: a
patch over browser APIs that re-checked *every* component's bindings after *any* event, timeout,
or HTTP response - correct, but brute-force, and the source of the legacy `ExpressionChanged
AfterItHasBeenCheckedError` you may meet in older code. Signals give Angular precise knowledge of
what changed, and current Angular can run **zoneless**, updating only actual dependents. In
inherited codebases both worlds coexist: class fields without signals still work (zone.js catches
them); new code should be signals for precision and for a future that's already arriving.

## effect(): the escape hatch, rationed

```ts
import { effect } from '@angular/core';

export class Cart {
  items = signal<number[]>([]);

  constructor() {
    effect(() => {
      localStorage.setItem('cart', JSON.stringify(this.items()));
    });
  }
}
```

*What just happened:* the effect runs once, tracks what it read (`items`), and re-runs on change -
here syncing state to a system *outside* Angular (storage). That's the entire legitimate territory:
localStorage, logging, third-party libraries, manual DOM. The framework is blunt about the
boundary: **writing to signals inside an effect throws by default** - because "state syncing
state" is a `computed` wearing the wrong costume, one render behind and twice as confusable. Same
rule as React, Vue, and Svelte; Angular enforces it loudest. (Effects also accept an `onCleanup`
callback for the timer/subscription cases - the universal start-needs-a-stop discipline.)

## Recap

1. Read by calling (`items()`), write with `set`/`update`, derive with `computed` - chains wire
   themselves.
2. `update` must return a *new* object/array - signals compare by reference; in-place mutation is
   invisible.
3. The call syntax makes every dependency edge visible - Angular's trade of ergonomics for
   explicitness.
4. Zone.js is the legacy check-everything engine; signals are the precise modern one, and zoneless
   is where Angular is heading.
5. `effect()` faces outward only - writing signals inside one throws, and the fix is usually
   `computed`.

```quiz
[
  {
    "q": "cart.update(list => { list.push(item); return list; }) runs, but the template doesn't change. Why?",
    "choices": [
      "update() can't be used with arrays",
      "The same array reference came back - signals compare by reference, so no change was detected",
      "push is asynchronous",
      "The template forgot to call cart()"
    ],
    "answer": 1,
    "why": [
      "Arrays are fine - fresh ones.",
      null,
      "push is synchronous; the item is in the array - the signal just doesn't know.",
      "If the template didn't call the signal it would never have displayed the cart at all."
    ],
    "explain": "Signals detect change by reference equality. Mutate-and-return-same is invisible; return a new array ([...list, item]) so the reference changes and dependents re-run."
  },
  {
    "q": "What does Angular's signal-read-is-a-function-call syntax buy, compared to Vue's invisible proxy reads?",
    "choices": [
      "Faster reads at runtime",
      "Every dependency on reactive state is visibly marked in the code - no ambiguity about what's live",
      "It removes the need for computed",
      "It allows signals to work without TypeScript"
    ],
    "answer": 1,
    "why": [
      "Read cost is negligible either way - the trade is about legibility, not speed.",
      null,
      "computed is central to signals - the call syntax changes nothing there.",
      "TypeScript is mandatory in Angular regardless."
    ],
    "explain": "The parentheses are the tracking made visible: reviewers and tools can see exactly where code depends on reactive state. The cost is typing (); the trade is deliberate."
  },
  {
    "q": "An effect() that reads items() and writes a summary signal throws at runtime. What's Angular telling you?",
    "choices": [
      "Effects can only run in components, not services",
      "State derived from state belongs in computed() - effects are for the world outside the signal graph, and signal writes inside them are blocked by default",
      "The effect is missing its dependency array",
      "summary must be declared before items"
    ],
    "answer": 1,
    "why": [
      "Effects run fine in services (they need an injection context, which services have).",
      null,
      "Angular effects auto-track - no dependency arrays exist.",
      "Declaration order doesn't produce this error - the write itself does."
    ],
    "explain": "A value computed from other signals is a derivation: computed(). Effects exist for storage, logging, DOM, and libraries - and Angular enforces the boundary with an error rather than a lint warning."
  }
]
```


---

# Component Inputs and Outputs

An Angular app is a tree of components, and the tree needs plumbing: data flowing down, events
flowing up. Modern Angular expresses both as signal-based functions - `input()`, `output()`,
`model()` - which slot straight into the reactivity you learned last phase: an input *is* a
signal, so computeds and templates track it like any other. Same one-way philosophy as the rest of
the family; Angular's version comes with the types checked.

## input(): data down, as a signal

```ts
// product-card.ts
import { Component, input, computed } from '@angular/core';

@Component({
  selector: 'app-product-card',
  template: `
    <article [class.dimmed]="!inStock()">
      <h3>{{ name() }}</h3>
      <p>{{ displayPrice() }}</p>
    </article>
  `,
})
export class ProductCard {
  name = input.required<string>();
  price = input.required<number>();     // cents
  inStock = input(true);                // optional, with default

  displayPrice = computed(() => (this.price() / 100).toFixed(2) + ' €');
}
```

```html
<!-- parent template -->
<app-product-card name="Kettle" [price]="4900" [inStock]="false" />
```

*What just happened:* each `input()` declares a prop - `required` ones the compiler *refuses to
let a parent omit*, optional ones with defaults. At the call site, the phase 2 rule applies:
brackets bind expressions, bare attributes pass strings (`[price]="4900"` is the number;
`price="4900"` is a string, and with `input.required<number>()` the **build fails** - the
TypeScript payoff in action).

Because inputs are signals, `displayPrice` is just a `computed` over them: parent passes a new
price, the computed and the template update through the standard graph. No lifecycle hook to
intercept changes (`ngOnChanges`, the legacy way) - dependency tracking replaces it.

## output(): events up

```ts
import { Component, input, output } from '@angular/core';

@Component({
  selector: 'app-product-card',
  template: `
    <h3>{{ name() }}</h3>
    <button (click)="addToCart.emit(1)">Add</button>
  `,
})
export class ProductCard {
  name = input.required<string>();
  addToCart = output<number>();     // the payload type
}
```

```html
<!-- parent template -->
<app-product-card name="Kettle" (addToCart)="cart.add('kettle', $event)" />
```

*What just happened:* `output<number>()` declares a typed event; the child fires it with
`.emit(payload)`; the parent listens with the same `(round brackets)` used for DOM events, and
`$event` is the payload. The child announces intent, the parent owns the meaning - data down,
events up, with both directions in the class's first lines:

```mermaid
flowchart TD
  P["Parent - owns cart"] -->|"[price], [inStock]"| C[ProductCard]
  C -->|"(addToCart) $event"| P
```

💡 **Key point:** a component's `input()`/`output()` declarations *are* its documentation - typed,
compiler-checked, and complete. This is Angular's version of the interface-first habit, and it's
why sprawling Angular codebases stay navigable: the contract is always at the top of the class.

## model(): two-way, composed

For genuinely co-owned values - the search box, the rating widget - `model()` declares an input
and its matching output as one:

```ts
// star-rating.ts
import { Component, model } from '@angular/core';

@Component({
  selector: 'app-star-rating',
  template: `
    @for (n of [1, 2, 3, 4, 5]; track n) {
      <button (click)="value.set(n)">{{ n <= value() ? '★' : '☆' }}</button>
    }
  `,
})
export class StarRating {
  value = model(0);
}
```

```html
<!-- parent -->
<app-star-rating [(value)]="rating" />
```

*What just happened:* `model(0)` is a *writable* input - the child may `set` it, and each set
emits a `valueChange` event under the hood. The parent's banana-in-a-box `[(value)]` wires both
directions. It's the same de-sugaring as `[(ngModel)]` in phase 2, now for your own components -
and the same consent principle as Svelte's `$bindable`: two-way exists only where the child
explicitly declares a `model`, never by ambush.

## Reading the legacy dialect

Most existing code declares its interface with decorators; the mapping is one-to-one:

| Legacy | Modern | Notes |
|---|---|---|
| `@Input() name: string;` | `name = input<string>()` | legacy inputs are plain fields, not signals - read `this.name`, no parens |
| `@Input({ required: true })` | `input.required<string>()` | |
| `@Output() add = new EventEmitter<number>();` | `add = output<number>()` | both fire with `.emit(...)` |
| `ngOnChanges(changes) {...}` | a `computed` or `effect` over the input signal | the hook watched inputs by hand |

The practical wrinkle: in legacy components, inputs are ordinary properties - templates say
`{{ name }}` not `{{ name() }}`. When you're editing a component, check which dialect it speaks
before adding parentheses (or forgetting them); phase 7's cheat-card includes the two mismatch
errors this produces.

## Recap

1. `input()` declares props as signals - `required` enforced at build time, defaults for the
   rest; computeds over inputs replace `ngOnChanges`.
2. `output<T>()` + `.emit(payload)` sends typed events up; parents listen with `(eventName)` and
   read `$event`.
3. `model()` is the declared two-way: writable input + implicit `xChange` output, bound with
   `[(x)]`.
4. The input/output block at the top of a class is the component's compiler-checked contract.
5. Legacy dialect: `@Input`/`@Output` decorators, non-signal fields, `ngOnChanges` - translate on
   sight, don't mix within a component.

```quiz
[
  {
    "q": "A parent writes <app-badge [count]=\"5\" /> but the build fails: required input 'label' has no value. What's Angular enforcing?",
    "choices": [
      "All inputs must always be provided",
      "The child declared label = input.required(), and required inputs are checked at compile time",
      "Inputs must be strings unless bracketed",
      "The selector app-badge is not imported"
    ],
    "answer": 1,
    "why": [
      "Optional inputs with defaults omit freely - only required ones are enforced.",
      null,
      "That's the string-vs-expression rule; this error is about absence, not type.",
      "A missing import is the unknown-element error (phase 7), not a missing-input one."
    ],
    "explain": "input.required() moves 'this prop is mandatory' from documentation into the compiler. The parent that forgets it gets a build error, not an undefined at runtime."
  },
  {
    "q": "Legacy component: @Input() title: string. You write {{ title() }} in its template and get a runtime error. Why?",
    "choices": [
      "Legacy inputs need the async pipe",
      "Decorator inputs are plain properties, not signals - there's nothing to call; it's {{ title }}",
      "The parentheses conflict with the event-binding syntax",
      "title must be initialized before use"
    ],
    "answer": 1,
    "why": [
      "The async pipe unwraps observables (phase 6) - unrelated to plain fields.",
      null,
      "() in interpolation is a call, not an event binding - the call itself is the problem.",
      "Initialization affects the value, not the calling-a-string crash."
    ],
    "explain": "The two dialects differ exactly here: signal inputs read with parens, decorator inputs without. Check which dialect a component speaks before editing its template."
  },
  {
    "q": "When does model() beat a plain input() plus output() pair?",
    "choices": [
      "Whenever a component has both inputs and outputs",
      "When parent and child genuinely co-own one value - form-like widgets where [(x)] binding is the natural interface",
      "Always - it's the modern replacement",
      "When the value is an object rather than a primitive"
    ],
    "answer": 1,
    "why": [
      "Most components have both - a product card's price in and addToCart out are two different values, not one shared one.",
      null,
      "One-way input/output remains the default and the majority - model() is the declared exception.",
      "Shape of the value is irrelevant; ownership is the criterion."
    ],
    "explain": "model() is for the rating-widget/search-box case: one value both sides read and write. Everything else stays one-way - data down, events up, traceable."
  }
]
```


---

# Services and Dependency Injection

Ask "where does shared state live?" in React and you get a discussion. Ask in Angular and you get
one word: **a service.** Cart state, the HTTP layer, logging, the current user - anything more
than one component needs lives in a service class, and **dependency injection** (DI) delivers it.
DI is the most enterprise-scented term in this guide and the actual idea is small; ten minutes
here demystifies half of every Angular codebase you'll ever open.

## A service is a class with a marker

```ts
// src/app/cart.service.ts
import { Injectable, signal, computed } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CartService {
  private items = signal<{ id: string; qty: number }[]>([]);

  readonly count = computed(() => this.items().reduce((n, i) => n + i.qty, 0));

  add(id: string, qty = 1) {
    this.items.update(list => {
      const line = list.find(i => i.id === id);
      return line
        ? list.map(i => (i.id === id ? { ...i, qty: i.qty + qty } : i))
        : [...list, { id, qty }];
    });
  }
  clear() { this.items.set([]); }
}
```

*What just happened:* a plain class holds signals and the methods that update them - phase 3's
reactivity, outside any component. `@Injectable({ providedIn: 'root' })` registers it with the
injector: "root" means **one instance for the whole application**, created lazily the first time
someone asks. Note the shape: state `private`, a read-only `computed` exposed, mutations as named
methods - the service owns its invariants, and no component can reach in and corrupt the list.

## inject(): asking for it

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

@Component({
  selector: 'app-header',
  template: `<span>Cart ({{ cart.count() }})</span>`,
})
export class Header {
  cart = inject(CartService);
}
```

```ts
// product-page.ts
export class ProductPage {
  cart = inject(CartService);
  buy(id: string) { this.cart.add(id); }
}
```

*What just happened:* both components asked the injector for `CartService` and received **the same
instance**. `ProductPage.buy()` updates the signal; `Header`'s template - subscribed through
`cart.count()` - updates immediately. Shared state without a prop chain, an event bus, or a store
library: a singleton service plus signals *is* Angular's store pattern.

```mermaid
flowchart TD
  I[(Injector - one CartService)] --> H[Header reads count]
  I --> P[ProductPage calls add]
  I --> X[CheckoutPage reads items]
```

📝 **Terminology:** `inject()` must run in an **injection context** - field initializers and
constructors, practically speaking. Calling it later (in a click handler, a `setTimeout`) throws
`NG0203`. The pattern is always the same: grab dependencies at the top of the class, use them
anywhere. Legacy dialect: constructor parameters -
`constructor(private cart: CartService) {}` - same injector, older spelling, everywhere in
existing code.

## Why the indirection earns its keep

You could `import { cartService } from './cart'` - a module singleton, like our Svelte guide's
`.svelte.js` pattern. Angular routes it through the injector for two reasons that matter at its
scale:

- **Swappability without surgery.** The injector is a lookup, and lookups can be overridden per
  environment or per test: hand the test a `FakeCartService` and the components under test never
  know. With hard imports, that seam doesn't exist - which is why "how do I mock this?" is a
  question Angular teams rarely ask. This is the testability argument that made DI famous, and it
  was Angular's core bet from version one.
- **Scoping when you need it.** `providedIn: 'root'` covers most services, but a component can
  provide its *own* instance (`providers: [WizardState]` in the decorator), giving each component
  subtree a private copy - the per-tree-instance case (two wizards, each with private step state)
  that pure module singletons can't express.

The trade, stated fairly: a layer of indirection you carry everywhere, for a seam you mostly
exploit in tests and larger architectures. Angular decided the trade once, for everyone - very on
brand.

## Services beyond state

The same mechanism carries stateless concerns - and composes:

```ts
@Injectable({ providedIn: 'root' })
export class NotificationService {
  private messages = signal<string[]>([]);
  readonly current = this.messages.asReadonly();

  show(msg: string) {
    this.messages.update(m => [...m, msg]);
    setTimeout(() => this.messages.update(m => m.slice(1)), 3000);
  }
}

@Injectable({ providedIn: 'root' })
export class CartService {
  private notify = inject(NotificationService);   // services inject services

  add(id: string, qty = 1) {
    /* ...update items... */
    this.notify.show('Added to cart');
  }
}
```

Layered services - the HTTP layer injected by data services injected by feature services - are the
skeleton of every large Angular app. Phase 6 adds the innermost layer: `HttpClient`, itself
delivered by DI.

## Recap

1. Shared state and logic live in `@Injectable` service classes; `providedIn: 'root'` = one lazy
   app-wide instance.
2. `inject(ServiceClass)` in a field initializer delivers it; constructor-parameter injection is
   the legacy spelling of the same thing.
3. Singleton service + signals = Angular's built-in store: private state, exposed computeds, named
   mutations.
4. DI's payoff is the seam - swap implementations in tests without touching consumers; its cost is
   indirection.
5. Component-level `providers` scope an instance to a subtree - the per-widget-tree case.

```quiz
[
  {
    "q": "Header and ProductPage both inject(CartService) with providedIn: 'root'. ProductPage adds an item. Why does Header's badge update?",
    "choices": [
      "Angular broadcasts a change event between components that inject the same class",
      "Both hold the same singleton instance, and the badge reads a computed over the instance's signal",
      "inject() creates linked copies that sync automatically",
      "The router refreshes all components on state change"
    ],
    "answer": 1,
    "why": [
      "No events are involved - just one object and the signal graph.",
      null,
      "No copies exist - that's the point of root scope: one instance, delivered to all askers.",
      "The router navigates; it plays no role in reactivity."
    ],
    "explain": "providedIn: 'root' means one instance per app. Shared instance + signals = shared reactive state: any component's write flows to any component's read through the normal dependency graph."
  },
  {
    "q": "Calling inject(CartService) inside a click handler throws NG0203. What's the rule?",
    "choices": [
      "Services can't be used from event handlers",
      "inject() only works in an injection context - grab dependencies in field initializers or the constructor, then use them anywhere",
      "The service was missing providedIn: 'root'",
      "Click handlers run outside Angular's zone"
    ],
    "answer": 1,
    "why": [
      "Using an already-injected service in a handler is the normal pattern - it's the injecting that's position-sensitive.",
      null,
      "A missing registration gives a no-provider error, not a context error.",
      "Zones relate to change detection, not injection timing."
    ],
    "explain": "The injector needs to know who is asking, which it only knows during construction. Inject at the top of the class (cart = inject(CartService)); call cart.add() from wherever you like."
  },
  {
    "q": "What does routing dependencies through the injector buy over a plain import of a module singleton?",
    "choices": [
      "Faster instantiation",
      "A replaceable seam - tests and environments can substitute implementations without touching consumer code - plus per-subtree scoping via component providers",
      "Automatic persistence of service state",
      "It's required for signals to work"
    ],
    "answer": 1,
    "why": [
      "Instantiation cost is identical - lazy either way.",
      null,
      "Nothing persists unless you write it somewhere - services are in-memory objects.",
      "Signals work anywhere; plenty of signal state lives outside services."
    ],
    "explain": "DI's indirection is a lookup you can override: FakeCartService in tests, scoped instances per subtree in the app. That seam is the argument that justifies the ceremony."
  }
]
```


---

# HTTP and Just Enough RxJS

Here's the phase where Angular's reputation for difficulty actually lives. Fetch data in Angular
and you don't get a Promise - you get an **Observable**, a concept from the RxJS library that
Angular builds its async story on. The full RxJS is a deep discipline with a hundred operators.
The working subset you need to *use Angular productively* fits in this phase - and modern Angular
lets signals carry more of the weight than the old tutorials suggest.

## Setup, once

`HttpClient` arrives through DI (of course), enabled at bootstrap:

```ts
// app.config.ts
import { provideHttpClient } from '@angular/common/http';

export const appConfig = {
  providers: [provideHttpClient()],
};
```

Forget this and every injection of `HttpClient` throws a no-provider error - worth knowing before
it happens, since the error names the class, not the missing config line.

## Observable: a lazy stream you subscribe to

```ts
// products.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface Product { id: string; name: string; cents: number; }

@Injectable({ providedIn: 'root' })
export class ProductsService {
  private http = inject(HttpClient);

  list(): Observable<Product[]> {
    return this.http.get<Product[]>('/api/products');
  }
}
```

**What an observable actually is.** A recipe for producing values over time. Two facts carry
everything else:

- **It's lazy.** Calling `list()` sends *no request*. An observable is inert until someone
  **subscribes** - the subscription pulls the trigger. (A Promise, by contrast, is already
  running the moment it exists.)
- **It can deliver many values.** HTTP happens to deliver one response and complete, but the same
  type models router events, form value changes, websockets - streams. That generality is *why*
  Angular chose it, and why the API feels heavier than `fetch` for the simple case.

## Consuming: three ways, ranked

**1. `toSignal` - the modern default.** Convert the stream into the reactivity you already know:

```ts
// products-page.ts
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-products-page',
  template: `
    @if (products(); as list) {
      <ul>
        @for (p of list; track p.id) { <li>{{ p.name }}</li> }
      </ul>
    } @else {
      <p>Loading…</p>
    }
  `,
})
export class ProductsPage {
  private productsService = inject(ProductsService);
  products = toSignal(this.productsService.list());
}
```

*What just happened:* `toSignal` subscribes for you, exposes the latest value as a signal
(`undefined` until the response lands - hence the `@if` guard with its handy `as` alias), and
**unsubscribes automatically** when the component is destroyed. Everything downstream is phase 3:
computeds over it, templates tracking it.

**2. The `async` pipe - the template-side classic.** `{{ (products$ | async) }}` subscribes in
the template and cleans up on destroy. You'll see it constantly in existing code (the `$` suffix
on observable variables is convention, not syntax); it predates signals and remains fine.

**3. Manual `subscribe()` - the one with the foot-gun.**

```ts
ngOnInit() {
  this.productsService.list().subscribe(list => this.products = list);
}
```

Legal, common in older code, and the source of Angular's classic leak: **a subscription outlives
its component unless something unsubscribes.** For a single HTTP get (completes after one value)
the stakes are low; for long-lived streams (router events, form changes, intervals) an
un-unsubscribed component keeps reacting forever - the same undead-timer disease as every
framework, in observable clothing. Modern escape hatch if you must subscribe manually:
`takeUntilDestroyed()` piped in, which ties the subscription to the component's lifetime. But the
real advice is structural: **let `toSignal` or `async` own subscriptions; subscribe by hand only
when you genuinely need imperative control.**

## The operators worth knowing (all five of them)

RxJS operators transform streams, composed through `.pipe(...)`. The survival kit:

```ts
import { map, catchError, switchMap, debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { of } from 'rxjs';

// transform the payload
this.http.get<Product[]>('/api/products').pipe(
  map(list => list.filter(p => p.cents > 0)),
  catchError(() => of([] as Product[])),    // error → fallback value, stream survives
);

// the search-box classic: typed input → debounced, deduped, switched requests
search$.pipe(
  debounceTime(300),                        // wait for typing to pause
  distinctUntilChanged(),                   // skip identical queries
  switchMap(q => this.http.get<Product[]>(`/api/search?q=${q}`)),
);
```

*What just happened in that last pipe:* `switchMap` maps each query to a *new* HTTP observable and
- the crucial part - **cancels the previous request when a new query arrives.** That's the
out-of-order-response race (which every guide in this category has fought by hand with cancelled
flags) solved by the operator's semantics. This is RxJS at its best, and it's the real pitch for
learning more of it eventually: entire classes of async choreography become one word.

Beyond these five, learn operators when a problem demands one, not from a list. Phase 8 points at
where to go deeper.

## Recap

1. `provideHttpClient()` at bootstrap; `HttpClient` via `inject`; typed calls like
   `http.get<Product[]>(url)`.
2. Observables are lazy streams: no subscription, no request; many values possible - that's why
   they're not Promises.
3. Consume with `toSignal` (modern default, auto-cleanup) or the `async` pipe (template classic);
   manual `subscribe` needs a lifetime plan (`takeUntilDestroyed`).
4. Survival operators: `map`, `catchError`, `debounceTime`, `distinctUntilChanged`, `switchMap` -
   the last one cancels stale requests and is worth the price of admission alone.
5. Learn more RxJS on demand, not in advance.

```quiz
[
  {
    "q": "You call productsService.list() but no network request appears in DevTools. The method returns http.get(...). What's the missing ingredient?",
    "choices": [
      "The URL must be absolute",
      "Nothing subscribed - observables are lazy, and the request fires on subscription (toSignal, async pipe, or subscribe)",
      "provideHttpClient() enables requests only in production",
      "The get() call needs an await"
    ],
    "answer": 1,
    "why": [
      "Relative URLs work fine against the serving origin.",
      null,
      "The provider works in every mode - without it you'd get a loud injection error, not silence.",
      "Observables aren't awaited - that's Promise vocabulary; subscription is the trigger here."
    ],
    "explain": "An observable is a recipe, inert until subscribed. Wrap it in toSignal, pipe it through async, or subscribe - that moment is when the request leaves."
  },
  {
    "q": "A component manually subscribes to router events in ngOnInit and is created/destroyed many times as the user navigates. What's accumulating?",
    "choices": [
      "Nothing - subscriptions die with their component",
      "Live subscriptions - each instance subscribed and nothing unsubscribed, so dead components keep reacting",
      "Router history entries",
      "Change detection cycles"
    ],
    "answer": 1,
    "why": [
      "That's exactly what does NOT happen automatically with manual subscribe - it's why toSignal and async exist.",
      null,
      "History grows with navigation regardless - the leak is the reacting dead components.",
      "Change detection isn't accumulated by subscriptions - callbacks are."
    ],
    "explain": "Manual subscriptions outlive their components unless tied to the lifetime (takeUntilDestroyed) or replaced with toSignal/async, which clean up automatically. Long-lived streams make the leak real fast."
  },
  {
    "q": "In the debounced-search pipe, what does switchMap contribute beyond map?",
    "choices": [
      "It runs the requests in parallel for speed",
      "It maps each query to a new request AND cancels the previous in-flight one - late responses from old queries can't overwrite new results",
      "It caches responses per query",
      "It retries failed requests"
    ],
    "answer": 1,
    "why": [
      "Parallelism is mergeMap's behavior - and exactly what reintroduces the race.",
      null,
      "No caching is involved - dedupe of identical consecutive queries came from distinctUntilChanged.",
      "Retry is its own operator (retry) - switchMap is about switching allegiance to the newest inner stream."
    ],
    "explain": "switchMap = 'only the latest matters': each new query unsubscribes the previous inner observable, which for HTTP means cancelling the stale request. The classic race, solved declaratively."
  }
]
```


---

# When Angular Breaks

Angular fails loudly and bureaucratically: errors arrive with NG-numbers, long names, and - once
you can read them - unusually precise diagnoses. That's the good news: where our Vue and Svelte
phases hunted *silent* failures, most Angular failures announce themselves. The skill is
translation, and the translations cluster around the same handful of causes.

## The cheat-card

| Symptom / message | Almost always means | Fix |
|---|---|---|
| **NG8001: 'app-x' is not a known element** | Component used in a template but not in the `imports` array | Add it to the standalone component's `imports` |
| **NG0203: inject() must be called from an injection context** | `inject()` in a handler, timeout, or lifecycle body | Move to a field initializer or constructor (phase 5) |
| **NullInjectorError: No provider for X** | Service/feature never registered | `providedIn: 'root'` on the service, or the `provideX()` bootstrap call (e.g. `provideHttpClient()`) |
| **Signal write error inside effect/computed** | Deriving state with an effect, or mutating inside `computed` | It's a `computed` (phase 3); keep derivations pure |
| Template shows `() => ...` or `[object Object]` | Interpolating the signal/observable itself | Call the signal (`{{ x() }}`); pipe the observable (`{{ x$ \| async }}`) |
| `{{ title() }}` throws "title is not a function" | Calling a *legacy* decorator input like a signal | Check the dialect: `@Input()` fields read without parens (phase 4) |
| Screen updates late or only after clicking elsewhere | Mutating an object/array in place, or zone-era code paths | Return fresh references from `update` (phase 3) |
| Rows shuffle state after delete/reorder | `track $index` on a mutable list | `track item.id` (phase 2) |
| Callbacks firing on destroyed components | Manual `subscribe` without a lifetime | `toSignal`/`async` pipe, or `takeUntilDestroyed` (phase 6) |
| **ExpressionChangedAfterItHasBeenCheckedError** | Legacy zone-era code changing state mid-check | In new code, move the write out of rendering paths; in old code, see below |

Three of these deserve the walk-through.

## NG8001: the standalone tax

```text
NG8001: 'app-product-card' is not a known element
```

The single most common error in modern Angular, and it's a feature wearing an error's clothing.
Standalone components declare their template dependencies explicitly:

```ts
@Component({
  selector: 'app-products-page',
  imports: [ProductCard],        // ← without this line: NG8001
  template: `<app-product-card [price]="4900" name="Kettle" />`,
})
```

*Why it exists:* in the NgModule era, anything declared in the module was visible to every
component in it - convenient, and impossible to tell what any single component actually used.
Standalone flips that: each component's `imports` array is its complete dependency list. The error
is the compiler asking you to finish the sentence. Same cure for pipes and directives
(`imports: [DatePipe, FormsModule]`) - if the template uses it, the component imports it.

## The dialect mismatch pair

Phase 4 planted the flag; here's how it looks when it goes off. Two mirror-image errors:

- **`title is not a function`** - template says `{{ title() }}`, but this component declares
  `@Input() title: string`. Legacy inputs are plain fields: drop the parens.
- **`[object Object]` or a function body on screen** - template says `{{ count }}`, but `count`
  is a signal. Interpolating the signal object prints it; *calling* it reads it: `{{ count() }}`.

Neither is subtle once you know the tell: **before editing any component, check its dialect** -
signal functions (`input()`, `signal()`) mean parens in the template; decorators (`@Input()`) mean
none. Mixed teams migrating incrementally hit these weekly, and they're ten-second fixes with the
habit installed.

## The legacy ghost: ExpressionChangedAfterItHasBeenCheckedError

You'll meet this one in older codebases, and its infamy is why it earns a section even in a
modern guide. Zone-era Angular checked every binding after every event - then, in development
mode, checked *again* to verify nothing changed mid-check. Code that wrote state during rendering
(a getter with a side effect, a child mutating a parent's value during init) failed that second
check and produced this error - Angular's bluntest statement of the universal rule that
**rendering must not change state**.

In signal-era code the same sin surfaces earlier and clearer (the signal-write guards from
phase 3). If you hit the legacy error in old code: find the write that happens during change
detection - the error message names the binding - and move it into an event handler, lifecycle
hook, or effect. The bug is always the write's *timing*, never the value.

## Reading NG errors like a local

- **The number is a documentation key.** Every NG-code has a page at `angular.dev/errors/NG8001`
  (swap the code) with causes and fixes - among the best error docs in the industry. Paste the
  code before pasting the stack trace into a search engine.
- **Read the template position.** Compile-time errors point at file, line, and column *in the
  template* - the answer is usually at that exact spot, not in the TypeScript.
- **Trust the compiler's strictness.** A large share of "Angular is fighting me" moments are the
  type system catching a real mismatch early - `input.required` missing, a wrong payload type on
  an output. The fight is the feature.

## Recap

1. NG8001 = the template uses something the component didn't import - standalone components list
   their dependencies, completely.
2. NG0203 = `inject()` outside construction; NullInjectorError = nothing registered - both are
   DI's rules, not mysteries.
3. Dialect tells: signals read with parens, decorator inputs without - check before editing.
4. Signal-write guards and the legacy ExpressionChanged error enforce one law: rendering never
   writes state.
5. NG-numbers are lookup keys at angular.dev/errors - translate first, debug second.

```quiz
[
  {
    "q": "NG8001: 'app-star-rating' is not a known element - but the component definitely exists and compiles. What's missing?",
    "choices": [
      "The selector must start with a capital letter",
      "StarRating isn't in the using component's imports array - standalone components declare their template dependencies explicitly",
      "The component needs providedIn: 'root'",
      "star-rating.ts must be renamed to match the selector"
    ],
    "answer": 1,
    "why": [
      "Selectors are kebab-case by convention - casing isn't the issue.",
      null,
      "providedIn registers services with the injector; components enter templates through imports.",
      "File names are free - the compiler resolves classes, not paths-by-convention."
    ],
    "explain": "A standalone component's imports array is its complete template vocabulary. Existing and compiling isn't enough - the using component must import it."
  },
  {
    "q": "A migrated-halfway codebase shows {{ count }} rendering as [object Object] in one component and {{ title() }} throwing in another. What single habit prevents both?",
    "choices": [
      "Always use parentheses in templates",
      "Check each component's dialect first: signal declarations mean parens, decorator inputs mean none",
      "Convert every component to signals before touching templates",
      "Use the async pipe for all bindings"
    ],
    "answer": 1,
    "why": [
      "Parens on a legacy field is exactly one of the two crashes.",
      null,
      "A full migration is the long-term fix, not the habit that keeps you safe on Tuesday.",
      "The async pipe unwraps observables - neither of these is one."
    ],
    "explain": "The two errors are mirror images of one mismatch. Ten seconds of checking whether the class says signal()/input() or @Input() tells you exactly how the template must read it."
  },
  {
    "q": "In a legacy codebase, ExpressionChangedAfterItHasBeenCheckedError appears in development. What is Angular fundamentally objecting to?",
    "choices": [
      "The component tree is too deep",
      "Some code changed bound state during change detection - rendering must not write state",
      "zone.js is missing from the build",
      "A signal was read without being called"
    ],
    "answer": 1,
    "why": [
      "Depth is irrelevant - timing of a write is the whole story.",
      null,
      "This error comes FROM zone-era checking - its absence would preclude it.",
      "That produces the [object Object] display, not this error."
    ],
    "explain": "The dev-mode double check exists to catch writes that happen mid-render. Find the write the message names and move it to a handler, hook, or effect - same law signals now enforce upfront."
  }
]
```


---

# Where to Go Next

Because Angular ships whole, "where to go next" means something different here than in the React
and Vue guides: less shopping, more unboxing. The tools below are mostly already installed - the
question is which parts of the box to open, in what order, and which to leave shrink-wrapped until
a real need shows up.

## What you can already build

Components, signals, inputs/outputs, services, HTTP - that's a working application skillset.
Build something real with only this before unboxing further: a small inventory app, a dashboard
over a public API. One deliberate gap you'll feel immediately - multiple pages - is the first
unboxing below.

## First unboxing: the Router

The Router is in the box and its shape will feel familiar after phase 5 - it's configuration plus
DI:

```ts
// app.routes.ts
import { Routes } from '@angular/router';

export const routes: Routes = [
  { path: '', component: HomePage },
  { path: 'products/:id', component: ProductPage },
  { path: '**', component: NotFoundPage },
];
```

```ts
// product-page.ts - reading the :id, the signal way
import { input } from '@angular/core';

export class ProductPage {
  id = input.required<string>();   // with withComponentInputBinding(), route params bind to inputs
}
```

*What just happened:* routes map URL patterns to components; `<router-outlet />` in the root
template marks where they render; `routerLink` replaces `href` for no-reload navigation. The
`withComponentInputBinding()` bootstrap option makes route parameters arrive as ordinary
`input()`s - the phase 4 machinery, reused. Guards (can this user enter?), lazy loading (load
this route's code on demand), and nested routes are the depth - a follow-up guide's worth - but
the basics above carry you far.

## Second unboxing: reactive forms

Phase 2's `[(ngModel)]` handles the contact form. The moment forms grow validation rules,
dynamic fields, or cross-field logic, Angular's **reactive forms** are the in-box answer: the
form's structure lives in TypeScript as a `FormGroup` of `FormControl`s, with validators
attached, and the template binds to it. Testable without a DOM, composable, verbose - very
Angular. The decision line: `ngModel` for simple capture, reactive forms once validation logic
becomes real logic. (A follow-up guide will do them properly; the official guide covers the
mechanics well.)

## The map: pain → unboxing

| When you feel this | Reach for | In the box? |
|---|---|---|
| Multiple pages, URLs, back button | **Router** | ✓ |
| Forms with real validation logic | **Reactive forms** | ✓ |
| Repetitive async choreography beyond phase 6's five operators | **More RxJS** - learn per problem | ✓ |
| Ready-made accessible UI components | **Angular Material / CDK** | official add-on |
| Cross-app state with audit/history needs | **NgRx (or the lighter SignalStore)** | third-party |
| SEO or first-paint pressure on public pages | **Angular SSR** (`ng add @angular/ssr`) | official add-on |

Sizing notes, plainly:

- **Angular Material** is the closest thing to a default UI kit in any framework's ecosystem -
  official, accessible, themeable, and visually opinionated (Material Design, unless you invest
  in theming). Its underlying **CDK** (overlays, focus management, drag-drop) is valuable even if
  you skin your own components.
- **NgRx** is the Redux tradition in Angular: actions, reducers, effects, real ceremony. Phase 5
  already gave you the pattern that makes most apps not need it - a signal service *is* a store.
  NgRx earns its cost when state changes need auditability (devtools time-travel, event logs) or
  when many teams share one large state surface. Its newer **SignalStore** is the lighter middle
  path. Adopt on pain, never on résumé pressure.
- **SSR**: the server-in-front decision - our
  [Next.js guide's phase 1](../nextjs-from-zero/01-what-nextjs-actually-is.md) explains the
  reasoning framework-independently. Angular's version (`ng add @angular/ssr`, hydration
  included) is real and improving; it's also the least-traveled of the big frameworks' SSR paths.
  For SEO-critical greenfield work, weigh whether a meta-framework-first stack fits the job
  better; for adding SSR to an existing Angular app, the official path is the path.

## Additional resources

- [angular.dev](https://angular.dev) - the modern docs site; the tutorial track and the
  errors reference (phase 7's habit) are the two most-used sections.
- [angular.dev/guide/forms](https://angular.dev/guide/forms) - the reactive-forms guide, best
  read with a real form of yours open in the editor.
- [RxJS docs' operator decision tree](https://rxjs.dev/operator-decision-tree) - the sane way to
  find an operator when a problem appears, instead of memorizing the catalog.

## Recap

1. Unbox the Router first (routes + outlet + `routerLink`, params as inputs), reactive forms
   second (when validation becomes logic).
2. RxJS depth comes per-problem via the decision tree - phase 6's five operators remain the daily
   core.
3. Material/CDK is the default UI answer; NgRx waits for auditability pain (SignalStore as the
   middle path); SSR is the server-in-front decision in Angular clothing.
4. The box is big - open it pain by pain, and let angular.dev's errors reference stay within
   reach.

```quiz
[
  {
    "q": "A team of four builds an internal tool: phase-5-style signal services hold the state, and it works fine. A colleague insists NgRx is required for \"proper\" Angular. Per this guide, what's the sound response?",
    "choices": [
      "Adopt NgRx - it's the official state solution",
      "Signal services already are a store pattern; NgRx earns its ceremony when auditability or many-team state surfaces demand it - adopt on pain",
      "Replace services with component state to avoid the debate",
      "Use NgRx for new features and services for old ones"
    ],
    "answer": 1,
    "why": [
      "NgRx is third-party and situational - nothing about it is required or default.",
      null,
      "Shared state belongs in services - retreating to component state recreates the sharing problem.",
      "Two state architectures in one app is the worst of both - pick by need, not by feature age."
    ],
    "explain": "A providedIn-root service with signals is a store: single instance, reactive reads, controlled mutations. NgRx adds action logs, time travel, and structure for many hands - costs that need their pains present."
  },
  {
    "q": "With withComponentInputBinding() enabled, how does a routed component receive the :id from /products/:id?",
    "choices": [
      "Via a global RouteParams service it must poll",
      "As a normal input() - route params bind to component inputs",
      "Through a constructor string parameter named id",
      "By parsing window.location in ngOnInit"
    ],
    "answer": 1,
    "why": [
      "There's an ActivatedRoute service (the older way) - but nothing is polled, and the modern binding skips it.",
      null,
      "Constructors receive injected dependencies, not route strings.",
      "Reading location by hand bypasses the router entirely - and breaks on client-side navigation."
    ],
    "explain": "Route parameters arrive through the same input() machinery as parent-to-child props - one component interface, whether the caller is a template or the router."
  }
]
```
