In Angular, how do you declare component inputs with input() and input.required(), and how do they differ from the @Input decorator?
answer
- a function returning a signal
- call it to read it
- required checked by the compiler
- read-only, unlike a decorated field
basics
~10 sinput(default) and input.required<T>() declare inputs as read-only InputSignal fields you read by calling them, and a missing required binding is a compile error. @Input() marks a plain, reassignable property and remains fully supported.
solid answer
~40 s`input()` declares a signal input: `size = input(40)` is an `InputSignal<number>` you read as `size()`, `input<number>()` without a default is typed `number | undefined`, and `input.required<string>()` has no default and no `undefined` in its type. The parent's binding looks the same as for `@Input()`. Differences: the signal is read-only, so the child cannot overwrite it; derived state can be a `computed()` that follows it automatically; and `undefined` is honest in the type, with no `!` assertion. Both APIs support `alias`, `transform` and required inputs, and a required input the template does not bind fails the build with NG8008. The signal APIs have been stable since v19 and are recommended for new code; `@Input` stays fully supported.
code
ts · 12 linesimport { Component, computed, input } from '@angular/core';
@Component({
selector: 'app-avatar',
template: `<img [src]="src()" [width]="size()" [height]="size()" [alt]="label()" />`,
})
export class Avatar {
readonly src = input.required<string>();
readonly size = input(40);
readonly name = input<string>();
readonly label = computed(() => this.name() ?? 'User avatar');
}go deeper
Declare inputs with input(), input.required() and @Input(), and remember to call a signal input to read it.
Explain the typing differences, read-only InputSignal, alias, and that NG8008 catches a missing required binding at build time.
Plan a migration from @Input to signal inputs, replacing ngOnChanges code with computed() and handling inputs the child used to reassign.
Weigh consistency across a large codebase: when to run the automated migration, and how to keep mixed APIs readable meanwhile.
## Two ways to declare an input An **input** is a property of a component (or directive) that a parent can set from its template with a binding such as `[size]="64"`. Angular offers two APIs for declaring one. **Signal inputs**, the `input()` function from `@angular/core`, are the current recommendation. The `input`, `output` and `model` APIs were marked stable in **v19**: ```ts import { Component, input } from '@angular/core'; @Component({ selector: 'app-avatar', template: `<img [src]="src()" [width]="size()" [alt]="name()" />`, }) export class Avatar { readonly src = input.required<string>(); // InputSignal<string> readonly size = input(40); // InputSignal<number> readonly name = input<string>(); // InputSignal<string | undefined> } ``` **Decorator inputs**, `@Input()` on a class property, are the original API. They remain fully supported and are in most existing codebases: ```ts import { Component, Input } from '@angular/core'; @Component({ selector: 'app-legacy-avatar', template: `<img [src]="src" />` }) export class LegacyAvatar { @Input({ required: true }) src!: string; @Input() size = 40; } ``` The parent's template binding is identical for both: `<app-avatar [src]="user.photo" [size]="64" />`. ## How signal inputs behave - `input(default)` returns an **`InputSignal<T>`**; you read it by **calling** it: `this.size()` in code, `size()` in the template. - Without a default, `input<number>()` is typed `InputSignal<number | undefined>`, because the parent may never set it. - `input.required<T>()` has no default and does **not** include `undefined` in its type. - The signal is **read-only**: there is no `set()` or `update()`, so a child cannot overwrite what its parent bound. A component that must write back uses a model input instead. - Because it is a signal, anything derived from it with `computed()`, and the template itself, updates automatically when the parent binds a new value. - `input()` may only be called in a **class field initializer** of a component or directive; the compiler rejects other call sites because it records inputs statically. ## Required inputs are checked at build time For both APIs, a required input that a parent's template does not bind is a **compile-time** error, **NG8008**: *Required input 'src' from component Avatar must be specified.* A plain static attribute (`src="/u/7.png"`) counts as setting it. ## Side-by-side comparison | | `input()` / `input.required()` | `@Input()` | |---|---|---| | Type of the member | `InputSignal<T>` | the plain value `T` | | Reading | call it: `size()` | property access: `size` | | Required | `input.required<T>()` | `@Input({ required: true })` | | Alias | `input(0, { alias: 'avatarSize' })` | `@Input({ alias: 'avatarSize' })` or `@Input('avatarSize')` | | Transform | `{ transform: fn }` | `{ transform: fn }` | | Child can reassign | no, read-only | yes, any code can assign the field | | Reacting to change | `computed()`, the template, or `ngOnChanges` | `ngOnChanges` or a setter | ## Aliases The `alias` option changes the name used in templates without changing the class member: with `size = input(40, { alias: 'avatarSize' })` the parent writes `[avatarSize]="64"` while the class still reads `this.size()`. The docs advise avoiding aliases in general; they are useful for keeping an old public name during a rename or for avoiding a collision with a native DOM property name. ## Why the signal API is preferred 1. **Type honesty**: an optional input's `undefined` is in the type, and a required one's is not, without the `!` non-null assertion the decorator version needs. 2. **Derivations stay in sync**: `computed(() => this.size() * 2)` recomputes when the parent binds a new size, which removes most `ngOnChanges` code. 3. **No accidental writes**: the child cannot mutate its own input. An automated migration, `ng generate @angular/core:signal-input-migration`, converts `@Input` fields and updates their references, so mixed codebases are common: both APIs work side by side, even in one component.
- Can the avatar component do this.size.set(64) on a signal input?No. `InputSignal` exposes no `set` or `update`, so the call does not compile. Inputs flow from the parent. If the component needs its own writable copy it keeps separate local state, and if it must push a value back to the parent it declares a model input instead.
- Where does Angular report a required input that a parent forgets to bind?At build time. The template type-checker reports NG8008, naming the missing input and the component. A static attribute such as `src="/u/7.png"` counts as a binding. Separately, reading a required signal input before any value arrives, for example in the constructor, throws NG0950 at runtime.
- Can one component mix @Input fields and input() signals?Yes. Both register ordinary inputs, the binding syntax is the same, and the signal-input migration converts fields incrementally. The only difference inside the class is how each is read: property access for the decorator, a call for the signal.
saying these in an interview costs you the question
- A signal input can be updated from inside the child with set().
- Required inputs are only checked at runtime when the component renders.
- The @Input decorator is deprecated and will be removed.
- You read a signal input as a plain property, without calling it.
- input() can be called anywhere, such as inside ngOnInit.