skip to content

Inputs & Transforms

Signal inputs from input() and input.required(), the @Input decorator, aliases and transforms like booleanAttribute. Interviewers check that you treat inputs as read-only and react to changes.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Angular, how do you declare component inputs with input() and input.required(), and how do they differ from the @Input decorator?

level: juniorimportance: must knowfreq 76%

answer

  1. a function returning a signal
  2. call it to read it
  3. required checked by the compiler
  4. read-only, unlike a decorated field

basics

~10 s

input(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 lines
ts
import { 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

for a junior

Declare inputs with input(), input.required() and @Input(), and remember to call a signal input to read it.

for a middle

Explain the typing differences, read-only InputSignal, alias, and that NG8008 catches a missing required binding at build time.

for a senior

Plan a migration from @Input to signal inputs, replacing ngOnChanges code with computed() and handling inputs the child used to reassign.

for a principal

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.
open as a page

In Angular, when should a component react to input changes with ngOnChanges and SimpleChanges, and when with computed() over a signal input?

level: middleimportance: must knowfreq 62%

basics

~20 s

Use computed() over signal inputs to derive state declaratively; it stays current as any input changes. Use ngOnChanges when you need the previous value, the first-change flag, or still have @Input fields, since SimpleChanges carries previousValue, currentValue and firstChange.

open as a page

In Angular, why should a test or host code set a component's input with ComponentRef.setInput instead of assigning the field on componentInstance?

level: middleimportance: should knowfreq 40%

basics

~20 s

ComponentRef.setInput sets an input the way a template binding does: it works for signal inputs, runs transforms, triggers ngOnChanges and marks OnPush views for check. Assigning componentInstance fields does none of that and cannot set a read-only InputSignal.

open as a page

In Angular, what do the booleanAttribute and numberAttribute input transforms do, and why does <app-avatar size="64" disabled> need them?

level: middleimportance: should knowfreq 44%

basics

~20 s

They coerce bound values before the input stores them: booleanAttribute makes a present attribute true unless it is 'false', and numberAttribute parses strings, falling back to NaN. Static attributes pass strings, so size and disabled need them.

open as a page

Why does an Angular component throw NG0950 when a field initializer reads its input.required() signal, and how would you restructure it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Field initializers run in the constructor, before Angular sets any input, so reading an unset required input throws NG0950. Derive the value with computed(), read it in the template, or move one-time setup to ngOnInit.

open as a page