Input Properties
Components rarely live in isolation — a parent needs to feed data down to its children. In Angular, that one-way data flow from parent to child is expressed through inputs. Historically this meant the @Input() decorator on a class property; modern Angular (17.1+) adds a signal-based input() function that integrates cleanly with the reactivity system. This page covers both, plus required inputs, aliases, and value transforms.
Declaring an input with @Input
The classic approach is to decorate a public class field with @Input(). The field name becomes the attribute the parent binds to in the template.
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<article class="card">
<h3>{{ name }}</h3>
<p>{{ role }}</p>
</article>
`,
})
export class UserCardComponent {
@Input() name = '';
@Input() role = 'Member';
}
A parent binds to the inputs using square-bracket property binding. Anything inside [ ] is evaluated as an expression, so you can pass strings, objects, or component state:
<app-user-card [name]="user.fullName" [role]="user.title" />
To pass a literal string you can drop the brackets — name="Ada" binds the constant "Ada", whereas [name]="Ada" would try to read a property called Ada on the parent.
Signal-based inputs
Since Angular 17.1, the preferred style is the input() function. It returns a read-only signal, so the value participates in change detection automatically and can be consumed by computed() and effect() without extra wiring.
import { Component, input, computed } from '@angular/core';
@Component({
selector: 'app-user-card',
standalone: true,
template: `
<article class="card">
<h3>{{ name() }}</h3>
<p>{{ greeting() }}</p>
</article>
`,
})
export class UserCardComponent {
name = input(''); // InputSignal<string>
role = input('Member');
greeting = computed(() => `${this.name()} — ${this.role()}`);
}
Note the call syntax in the template: signal inputs are read with name(), not name. The parent binding is unchanged — [name]="user.fullName" works exactly the same way regardless of which API the child uses.
Required inputs
A signal input can be marked required, which makes the binding mandatory. If a parent renders the component without supplying it, the compiler reports an error at build time rather than silently passing undefined.
export class UserCardComponent {
userId = input.required<string>();
name = input('');
}
With the decorator API the equivalent is @Input({ required: true }) userId!: string;. Prefer input.required() in new code — the type is non-nullable, so you never have to guard against undefined.
Aliases
Sometimes the public binding name should differ from the internal property name — for example to match an HTML attribute convention or to avoid a naming clash. Use an alias.
export class UserCardComponent {
// Parent binds [user-id]; component reads this.id()
id = input.required<string>({ alias: 'user-id' });
}
<app-user-card [user-id]="user.id" />
The decorator form takes the alias as a string argument: @Input('user-id') id!: string;.
Aliases are easy to overuse. They add a layer of indirection that makes templates harder to trace back to the component class. Reach for one only when there’s a real reason — keeping the property and binding names identical is almost always clearer.
Input transforms
A transform lets you normalise or coerce an incoming value before the component stores it. The most common use is coercing string-y attribute values into the type you actually want. Angular ships two ready-made transforms, booleanAttribute and numberAttribute.
import { Component, input, booleanAttribute, numberAttribute } from '@angular/core';
@Component({
selector: 'app-toggle',
standalone: true,
template: `<button [disabled]="disabled()">{{ count() }}</button>`,
})
export class ToggleComponent {
// Bare attribute `disabled` now means `true`
disabled = input(false, { transform: booleanAttribute });
count = input(0, { transform: numberAttribute });
}
<!-- No brackets, no value: booleanAttribute resolves this to true -->
<app-toggle disabled count="5" />
Output:
The button renders disabled with the label "5".
You can also supply your own transform — any pure function from the accepted input type to the stored type:
title = input('', { transform: (value: string) => value.trim().toUpperCase() });
Reacting to input changes
With signal inputs, reacting to a change is just reading the signal inside a computed() or effect(). With the decorator API you implement the OnChanges lifecycle hook.
import { Component, input, effect } from '@angular/core';
@Component({ selector: 'app-logger', standalone: true, template: '' })
export class LoggerComponent {
value = input(0);
constructor() {
effect(() => console.log('value changed:', this.value()));
}
}
API comparison
| Feature | @Input() decorator | input() signal |
|---|---|---|
| Read in template | {{ name }} | {{ name() }} |
| Required | @Input({ required: true }) | input.required<T>() |
| Alias | @Input('alias') | input(d, { alias }) |
| Transform | @Input({ transform }) | input(d, { transform }) |
| Reactivity | manual / OnChanges | automatic (signal) |
| Writable | mutable field | read-only |
Best Practices
- Prefer the signal
input()API in new standalone components — it removesOnChangesboilerplate and composes withcomputed()/effect(). - Use
input.required<T>()for values the component genuinely cannot function without; it gives you a non-nullable type and a compile-time guarantee. - Treat inputs as read-only. Never mutate an input from inside the child; derive new state with
computed()instead. - Provide sensible default values for optional inputs so the component renders safely before bindings arrive.
- Use
booleanAttributeandnumberAttributetransforms for attribute-style bindings rather than parsing strings by hand. - Avoid aliases unless an external naming convention forces one — matching property and binding names keeps templates legible.