Skip to the solution
HardFrontend9 min read

What is model() — two-way signal binding with model inputs?

801 views

The problem

Question presented to candidate: "Show me how model() works. If a child declares rating = model(0), what does the parent bind, what actually gets emitted and when, and what goes wrong if the parent only binds one way?"

What a strong answer should cover:

  • model() declares an input rating and an output ratingChange from one field; [(rating)]="sig" binds both.
  • The output fires only when the child sets a different value; writes coming from the parent do not echo back.
  • [(rating)] takes the signal itself (sig, not sig()); a plain field also works.
  • One-way [rating] lets the child drift from the parent until the parent sends a different value.
  • model.required(), alias (the output becomes <alias>Change), and programmatic subscribe.
  • When to prefer input() + output() or a form control instead.

Clarifying questions expected:

  • "Is the parent value a signal or a plain field?"
  • "Does this need to plug into reactive forms as well?"

Code / implementation expected: Yes — a child with model() used two-way, one-way and against a plain field.

  • model
  • two-way
  • signals

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

Angularmodel.required(): two-way to a signal, one-way, and two-way to a plain field (needs the Angular compiler)
import { Component, model, signal } from "@angular/core";

// CHILD: one model.required() field = an input "rating" + an output "ratingChange".
@Component({
  selector: "star-rating",
  template: `
    @for (n of [1, 2, 3, 4, 5]; track n) {
      <button (click)="rating.set(n)">{{ n <= rating() ? "*" : "." }}</button>
    }
  `,
})
export class StarRatingComponent {
  rating = model.required<number>();

  constructor() {
    // A model is also an OutputRef: you can listen to its Change event in code.
    this.rating.subscribe(v => console.log(`  ratingChange emitted ${v}`));
  }
}

@Component({
  selector: "app-root",
  imports: [StarRatingComponent],
  template: `
    <p>A two-way to a signal: <star-rating [(rating)]="sig" /> parent={{ sig() }}</p>
    <p>B one-way only:        <star-rating [rating]="oneWay()" /> parent={{ oneWay() }}</p>
    <p>C two-way to a field:  <star-rating [(rating)]="plain" /> parent={{ plain }}</p>
    <button (click)="sig.set(1); oneWay.set(1)">Parent sets 1</button>
  `,
})
export class AppComponent {
  sig = signal(3);
  oneWay = signal(3);
  plain = 3; // works too: the compiler falls back to plain assignment
}

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 94 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 zoneless Angular and how does it improve performance in Angular 18?

Continue