Signal Inputs
Signal inputs are the modern, signal-based replacement for the @Input() decorator. Instead of a plain class property that Angular mutates behind the scenes, an input declared with the input() function becomes a read-only signal — a first-class reactive value you can read in templates, derive computed() values from, and react to inside effect(). This closes the long-standing gap between component inputs and the rest of the signals world, giving you fully reactive, type-safe component APIs without ngOnChanges or manual change tracking.
Declaring a basic input
You create a signal input by calling input() from @angular/core and assigning the result to a class field. The argument you pass is the default value used when the parent omits the binding. The returned value is an InputSignal<T> — a read-only signal, so you read it by calling it, but you cannot set() or update() it from inside the component.
import { Component, input } from '@angular/core';
@Component({
selector: 'app-greeting',
standalone: true,
template: `<p>Hello, {{ name() }}!</p>`,
})
export class GreetingComponent {
// InputSignal<string>, defaults to 'World'
name = input('World');
}
A parent binds to it exactly like a classic input:
<app-greeting [name]="userName" />
<app-greeting /> <!-- falls back to 'World' -->
Because name is a signal, any template expression, computed, or effect that reads it automatically re-runs when the bound value changes. There is no need to implement OnChanges.
Required inputs
When a component cannot function without a value, use input.required(). This produces an InputSignal<T> with no default, and Angular enforces the binding at build time — the template type checker reports an error if a consumer forgets to provide it. Because there is no initial value, you must supply the type via the generic parameter.
import { Component, input, computed } from '@angular/core';
@Component({
selector: 'app-user-badge',
standalone: true,
template: `<span class="badge">{{ initials() }}</span>`,
})
export class UserBadgeComponent {
// No default — must be bound by the parent
fullName = input.required<string>();
initials = computed(() =>
this.fullName()
.split(' ')
.map((part) => part[0])
.join(''),
);
}
Reading a required input before Angular has set it (for example in the constructor) throws a runtime error. Read required inputs from templates,
computed,effect, or lifecycle hooks such asngOnInit— never in field initializers that run during construction.
Aliases
By default the public binding name matches the field name. Pass an alias in the options object to expose a different name to consumers while keeping a clearer internal field name. This is the signal-based equivalent of @Input('alias').
@Component({ /* ... */ })
export class SliderComponent {
// Consumers bind [value], the class uses currentValue
currentValue = input(0, { alias: 'value' });
}
<app-slider [value]="50" />
Avoid aliases unless you have a strong reason — a mismatch between the binding name and the field name makes components harder to read. Prefer renaming the field instead.
Input transforms
A transform function runs every time the input is set, converting the value the parent provides into the value the component stores. This is ideal for coercing booleans and numbers from string attributes, or normalising data. The signal’s type reflects the transformed (output) type, while the accepted binding type is the transform’s parameter type.
import { Component, input } from '@angular/core';
import { booleanAttribute, numberAttribute } from '@angular/core';
@Component({
selector: 'app-button',
standalone: true,
template: `
<button [disabled]="disabled()" [style.--w.px]="width()">
<ng-content />
</button>
`,
})
export class ButtonComponent {
// "" or "true" attribute -> true
disabled = input(false, { transform: booleanAttribute });
// "120" string attribute -> 120 number
width = input(0, { transform: numberAttribute });
// Custom transform: trim and lowercase
variant = input('default', {
transform: (value: string) => value.trim().toLowerCase(),
});
}
<app-button disabled width="120" variant=" PRIMARY ">Save</app-button>
Output:
<button disabled style="--w: 120px">Save</button>
<!-- variant() returns "primary" -->
The built-in booleanAttribute and numberAttribute helpers handle the common HTML-attribute coercion cases that previously required coerceBooleanProperty from the CDK.
Reacting to input changes
Since inputs are signals, you compose them with the rest of the reactive graph rather than using ngOnChanges. Derive synchronous values with computed(), and run side effects with effect().
import { Component, input, computed, effect } from '@angular/core';
@Component({
selector: 'app-price',
standalone: true,
template: `<p>{{ formatted() }}</p>`,
})
export class PriceComponent {
amount = input.required<number>();
currency = input('USD');
formatted = computed(() =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: this.currency(),
}).format(this.amount()),
);
constructor() {
effect(() => console.log('amount changed:', this.amount()));
}
}
Option reference
| Option | Type | Applies to | Purpose |
|---|---|---|---|
| default | T (first argument) | input() only | Value used when the binding is omitted |
alias | string | both | Public binding name shown to consumers |
transform | (v: U) => T | both | Converts/coerces the incoming value |
Best practices
- Reach for
input.required()whenever a missing value would be a bug — let the template type checker catch it at build time instead of guarding at runtime. - Never read a required input in a field initializer or constructor body; defer to templates,
computed,effect, orngOnInit. - Use
booleanAttributeandnumberAttributefor boolean/numeric attribute inputs so authors can writedisabledinstead of[disabled]="true". - Build derived state with
computed()from inputs rather than recomputing in the template or duplicating it into local fields. - Keep transforms pure and cheap — they run on every set and should not trigger side effects.
- Avoid aliases unless integrating with an existing public API; matching field and binding names keeps components self-documenting.
- For two-way binding scenarios where the child must write back, use
model()instead ofinput().