When signals landed in Angular, the place they instantly “clicked” for me was inside components. Instead of juggling @Input(), local state, OnPush, markForCheck(), and RxJS everywhere, signals gave a single, consistent way to drive UI updates. In this part, the focus is on how signals work inside components and templates, and how they unlock faster, more predictable change detection—especially in zoneless setups.
How Signals Drive Change Detection (Without Relying on Zone.js)
Traditionally, Angular leans on Zone.js to monkey‑patch async APIs (clicks, timers, HTTP, etc.) and then run change detection across large parts of the component tree. That “magic” works, but it often checks more than it really needs. Signals flip this model: the template becomes a consumer of signals, and Angular knows exactly which view depends on which signals. When a signal value changes, Angular marks only those consumers as dirty and schedules a targeted refresh instead of walking everything.
In zoneless mode, this becomes even more powerful. Instead of reacting to every async event globally, Angular reacts to signal changes, which you control explicitly with set(), update(), or mutate(). This is why signals improve performance: fewer unnecessary checks, more precise updates, and better scalability as your component tree grows.
Using Signals as Component State
Inside a component, a signal can completely replace ad‑hoc local state and a lot of simple RxJS usage. The pattern is:
- Declare a signal as your state source.
- Use that signal in your template.
- Update the signal in event handlers or effects.
Example (conceptual):
- count = signal(0) → standard local state.
- doubleCount = computed(() => count() * 2) → derived UI data.
- effect(() => console.log(count())) → side effects that react to changes.
Using Signals in Templates
Templates treat signals like “functions you call to read a value.” Any time the signal changes, the bound part of the template gets re‑evaluated:
- {{ counter() }} instead of {{ counter }}.
- [disabled]="isSaving()".
- *ngIf="items().length > 0".
In OnPush components, this behaviour is especially attractive: signals bound in the template act as fine‑grained triggers, avoiding manual markForCheck() in many scenarios.
Signal Inputs in Angular v17+
Angular 17+ introduces signal inputs, which expose @Input() values as signals instead of plain properties. That means:
- Parents bind to a child as usual.
- Inside the child, the bound value is a read-only signal you can use in computations and effects.
Signal inputs help in several ways:
- No more ngOnChanges just to react to @Input() changes—computed signals and effects cover that use case.
- Better type safety and a more unified mental model across local and incoming state.
- Easier composition with other signals, such as combining an input signal with internal state in a computed().
Signal‑Friendly Outputs (New Output API)
Alongside signal inputs, Angular ships a new output() API that aligns conceptually with signal‑style component authoring. While output() itself is not a signal under the hood, it plays nicely with signal components by providing a function-based, typed way to emit values up to parents. This reduces boilerplate compared to @Output() EventEmitter and pairs naturally with signal‑driven state inside the component.
The result is a cleaner, more consistent component authoring experience: signals for reading state, input() for incoming data as signals, and output() for pushing events back up.
Why This Matters for Real Apps
- Signals give components a straightforward, ergonomic state model.
- Templates react automatically and precisely to signal changes.
- Change detection becomes more predictable, less tied to Zone.js, and easier to reason about.
- Signal inputs/outputs allow you to design components that are reactive end‑to‑end, without mixing too many paradigms.
