skip to content

In Angular, when would you declare model() instead of an input() plus output() pair, and when is the pair still better?

level: middleimportance: must knowfreq 55%

answer

  1. who owns the state
  2. child writes vs child requests
  3. no transform on models
  4. same name plus Change
  5. event payload other than value

basics

~20 s

Use model() when the child edits one value the parent mirrors through [( )]; use input() plus output() when the parent must approve changes, the event carries something other than the value, or the input needs a transform.

solid answer

~40 s

`model()` bundles a writable input and its `<name>Change` output, so the child can change the value itself and a `[( )]` binding keeps the parent in sync: ideal for a toggle, slider or date picker. An `input()` plus `output()` pair keeps the input read-only in the child; the child emits a request and the parent decides whether to change the bound value, which is the right design when the parent validates, rejects or transforms the change. The pair is also needed when the event carries something other than the new value (a reason, a diff), when you want a transform such as `booleanAttribute` (models do not support transforms), or when the event name should describe intent, like `closeRequested`. Both forms support `[( )]` only if the output is named `<input>Change`.

go deeper

for a junior

Know that model() gives the child a writable value plus a Change output, while input() is read-only in the child.

for a middle

Explain the state-ownership difference: a model shows the child's write immediately, the pair keeps the parent as the single source of truth.

for a senior

Choose per component: models for self-contained editors, the pair for validated or vetoed changes, intent-named events and transformed inputs.

for a principal

Set a component-library rule for which controls are controlled versus self-owning, so consumers can predict whether a child can change a value.

## Two ways to let a child change a parent's value In Angular a parent passes data down through inputs and hears back through outputs. When a child component edits a value that the parent also holds, there are two ways to author the child: - **`model()`**: one field that is a writable signal input plus an implicit output named `<field>Change`. The child calls `set()` or `update()`, its own value changes immediately, and the new value is emitted. - **`input()` plus `output()`**: a read-only `InputSignal` for the value and a separate `OutputEmitterRef` for events. The child cannot write its input; it emits, and the parent chooses what the input becomes next. Both can be used with the `[( )]` syntax, provided the output is named exactly after the input with `Change` appended. The difference is **who owns the state and who can veto a change**. ## Side by side | Concern | `model()` | `input()` + `output()` | |---|---|---| | Child may write the value | yes, `set()` / `update()` | no, `InputSignal` has no `set` | | Output declared | implicit `<name>Change` | explicit, any name | | Event payload | always the new value | anything you choose | | Input `transform` | not supported | supported (`booleanAttribute`, `numberAttribute`, custom) | | Works with `[( )]` | always | only if output is `<input>Change` | | Parent can reject a change | only by pushing a different value back | naturally: it simply does not update | | Child's view when parent ignores the event | shows the child's own write | still shows the parent's value | The last row is the practical heart of it. With `model()`, the child's display changes the moment it writes, whether or not the parent listens. With the pair, the child keeps rendering whatever the parent bound, so the parent is the single source of truth. ## When model() is the right call 1. The component's purpose is to edit one value for its user: a checkbox, a rating, a colour swatch, a collapsible panel's open state. 2. Any value the child produces is acceptable to the parent as-is. 3. The component should also work standalone, keeping its own state when nobody binds it: `model(false)` works with no binding at all. It removes boilerplate and one class of bug: forgetting to keep an input and a manually named output in step. ## When the pair is still better - **The parent must validate or veto.** A quantity stepper whose parent enforces stock limits should emit `quantityChange` as a request and render whatever the parent sends back. With a model, the child would show the rejected value until the parent pushed a correction. - **The event is not the new value.** A dialog that emits `closed` with a reason, or a table that emits a sort descriptor, is not a two-way value. - **The input needs coercion.** `disabled = input(false, {transform: booleanAttribute})` lets `<app-x disabled>` work. A model input has no `transform` option, because what goes out on the change output must be the same type as what came in. - **The event name should describe intent.** `expandRequested` reads better than a value echo when the parent decides the outcome. ## A controlled variant with the pair ```ts import {Component, input, output} from '@angular/core'; @Component({ selector: 'app-qty', template: `<button (click)="qtyChange.emit(qty() + 1)">{{ qty() }} +</button>`, }) export class Qty { qty = input.required<number>(); qtyChange = output<number>(); // name ends in Change, so [(qty)] still works } ``` Here `<app-qty [(qty)]="count" />` behaves like a model for a permissive parent, but a parent can instead bind `[qty]="count()" (qtyChange)="tryChange($event)"` and refuse values above the stock level. The child never shows a number the parent did not accept. ## A note on the decorator era Before `model()` (added in 17.2, public API since 19.0), a two-way property was written as `@Input() value` plus `@Output() valueChange = new EventEmitter()`, and the child usually kept a private copy to render. That pattern still compiles in Angular 22.2 and is common in older codebases. Migrating it to `model()` is appropriate where the child really owned the value; where the parent vetoed changes, the signal equivalent is `input()` plus `output()`. ## How interviewers probe this - Ask which of the two a custom form-like control should use: usually `model()` for its main value. - Ask how to accept `<app-toggle checked>` as an attribute: it needs a transform, which pushes you to `input()`. - Ask what the child displays if the parent ignores the change event: the model's own write, or the pair's unchanged input.

  • Can a model input use booleanAttribute so that a bare attribute means true?
    No. `model()` accepts only `alias` and `debugName`; input transforms are not supported on model inputs. If you need attribute coercion, declare an `input()` with `transform: booleanAttribute` and a separate output, or keep the model and have callers bind `[(checked)]` to a real boolean.
  • With input() plus output(), how does the child show a pending edit without writing its input?
    It keeps local state derived from the input, for example a `linkedSignal` seeded by the input that resets when the parent pushes a new value, and emits when the edit is committed. The input stays the parent's value and the local signal holds the draft.
  • Does [(value)] require model(), or will any input work?
    Any input works if an output with the same name plus `Change` exists on the same component: that is all the two-way syntax needs. `model()` simply declares both halves in one field and lets the child write the input side itself.

saying these in an interview costs you the question

  • [(value)] only works with model(), never with an input and output pair
  • With model() the parent can still veto a child's write before it shows
  • A model input supports transform just like input()
  • An input() can be written by the child with set() if it needs to
  • model() is always better, so input plus output is legacy