Skip to the solution
HardFrontend9 min read

What are signal inputs and model() in modern Angular?

1.0k views

The problem

Question presented to candidate: "Modern Angular components declare input(), model() and output() instead of decorators. Explain what signal inputs and model() are, and how you decide which one a given property should be."

What a strong answer should cover:

  • A signal input (input()) is a read-only signal: the parent writes it through a binding, the child only reads it.
  • A model input (model()) is a writable signal that is also an input plus an output named <name>Change, so the parent can bind it with [(name)].
  • The decision is about ownership: parent owns it (input), child and parent share it (model), child only reports an event (output).
  • Derived values from either are computed(); neither needs ngOnChanges.
  • A local, resettable copy of an input is linkedSignal, not a model.
  • Both were developer preview in 17.x and are stable since v19.

Clarifying questions expected:

  • "Should the parent always know the current value, or only be told when the user commits a change?"

Code / implementation expected: Yes β€” a small child with one input() and one model(), bound from a parent.

  • signals
  • inputs

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 10 sections

Read the code

AngularA stepper with input() for the limit and model() for the value (needs the Angular compiler)
import { Component, computed, input, model, signal } from "@angular/core";

// CHILD: input() for data it only reads, model() for the value it may change.
@Component({
  selector: "qty-stepper",
  template: `
    <button (click)="step(-1)" [disabled]="value() <= 0">-</button>
    <span>{{ value() }}</span>
    <button (click)="step(1)" [disabled]="atMax()">+</button>
  `,
})
export class QtyStepperComponent {
  max = input(5);         // read-only for the child: the parent owns it
  value = model(0);       // writable by the child AND bindable with [(value)]

  atMax = computed(() => this.value() >= this.max());

  step(delta: number) {
    this.value.update(v => v + delta); // writing a model also emits valueChange to the parent
  }
}

@Component({
  selector: "app-root",
  imports: [QtyStepperComponent],
  template: `
    <qty-stepper [(value)]="cups" [max]="3" />
    <p>Parent sees {{ cups() }} cups, total {{ cups() * 2 }} EUR</p>
    <button (click)="cups.set(0)">Reset from parent</button>
  `,
})
export class AppComponent {
  cups = signal(1);
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 85 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextHard

What is the purpose of the KeyValueDiffers and IterableDiffers services?

Continue