Lesson 8 of 30
output() replaces @Output()/EventEmitter for one-way events out of a component. model() goes further — it's a two-way-bindable signal that makes [(x)] syntax work.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
The old way to send data out of a component was @Output() saved = new EventEmitter<Item>();, then this.saved.emit(item) when something happened. It worked, but it dragged RxJS's EventEmitter into a role that was never really about streams — most outputs just fire once per user action, not a continuous sequence.
output() is the function-based replacement: saved = output<Item>();, then this.saved.emit(item) — same .emit() call, but declared without a decorator or EventEmitter import, and typed more precisely as "a thing you emit," not "an Observable you could .pipe() into."
model() solves a different, more specific problem: two-way binding. A signal input can't be written back to by the child, and a plain output only goes one direction — so to build something like a custom <app-toggle [(checked)]="isOn"> component, you'd normally need to declare both an input checked and an output checkedChange by hand, matching that exact naming convention yourself. model() does both halves in one declaration: checked = model(false) gives you a signal the child can both read and write with .set(), and Angular automatically wires up the paired checkedChange output for you — which is what makes [(checked)] two-way syntax work on the parent side at all.
import { Component, output, model } from '@angular/core';
@Component({
selector: 'app-star-rating',
standalone: true,
template: `
<div class="stars">
@for (star of [1, 2, 3, 4, 5]; track star) {
<button
type="button"
[class.filled]="star <= value()"
(click)="select(star)"
>★</button>
}
</div>
`,
})
export class StarRatingComponent {
// two-way: parent can bind [(value)]="myRating", and this component can
// both read value() and write it back with value.set(...)
value = model(0);
// one-way out only: fires once, doesn't carry a "current value" round-trip
rated = output<{ stars: number; timestamp: number }>();
select(star: number) {
this.value.set(star);
this.rated.emit({ stars: star, timestamp: Date.now() });
}
}
A parent uses it as <app-star-rating [(value)]="productRating" (rated)="logAnalytics($event)" />. [(value)] works because model() silently created a valueChange output alongside the value signal — Angular's two-way binding syntax is just shorthand for [value]="productRating" (valueChange)="productRating = $event", and model() is what makes both halves exist without you writing them separately. rated, by contrast, is a plain output() — it's a one-time analytics event, not state the parent should treat as a mirrored value, so output() is the right tool there and model() would be overkill.
output() is not a signal. Unlike input() and model(), output() doesn't return something you read with () — it returns an emitter you call .emit() on. It's still the modern replacement for @Output(), just not itself part of the signal-reading API.model()'s auto-generated output name is not customizable beyond the <name>Change convention. checked = model(false) always pairs with checkedChange — there's no option to rename it, because Angular's [(x)] syntax sugar specifically expects that exact suffix to desugar correctly.model() value can be changed by the child at any time, not just in response to the parent. That's the whole point — but it also means a child component can silently drift a parent's bound state without the parent explicitly calling a setter, which is worth designing for deliberately, not accidentally.output() is doing the same conceptual job as a callback prop in React (onSave={handleSave}) — a function-shaped way to notify a parent, instead of RxJS's stream-shaped EventEmitter. model() has no direct React equivalent in the same syntactic form — React has no built-in two-way binding sugar — but it's solving the same underlying "controlled component that can also update itself" pattern React developers build by hand with a value prop plus an onChange callback.