Dec 11, 2025

Angular Signals Part 2: Components, Templates & Zoneless Change Detection

Tags

When signals landed in Angular, the place they instantly “clicked” for me was inside components. Instead of juggling @Input(), local state, OnPushmarkForCheck(), 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.


https://assets.codevichar.com/articleangular-signal-component-template-zoneless-change-detection

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.​
This keeps state, derivations, and reactions all within a single, consistent mental model.


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".


Because the template reads signals directly, Angular can track those reads and register the view as a consumer. When a signal updates, Angular knows that this view depends on it and refreshes only what’s necessary. This is very similar in spirit to using an Observable with the async pipe, but you skip the subscription boilerplate.​


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().​


You can also mark signal inputs as required or optional and apply transformations, which makes them fit nicely into component APIs built around signals.


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


Putting it all together:


  • 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.​


In Part 3, the focus will shift to signals for app‑wide state management and forms—replacing BehaviorSubject stores, building a simple cart/todo store, and wiring reactive forms with effect() so your state and UI feel tightly connected.


EmoticonEmoticon