100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TypeScript

Signal Inputs and model()

Learn Angular's signal-based input() and model() APIs, the modern replacement for @Input()/@Output() that integrates natively with signals and two-way binding.

Component CommunicationIntermediate10 min readJul 9, 2026
Analogies

Signal Inputs and model()

Angular 17.1 introduced signal inputs — a function-based API, input() and input.required(), that replaces @Input() with a value exposed as a readonly Signal rather than a plain class property. Angular 17.2 followed with model(), which builds on signal inputs to provide a two-way-bindable signal, replacing the older @Input()/@Output() pair pattern used for banana-in-a-box syntax ([(ngModel)]-style custom two-way bindings). These APIs are part of Angular's broader shift toward signals as the primary reactivity primitive, and as of Angular 17+ they are the recommended default for new component inputs, offering better type safety, explicit reactivity, and tighter integration with computed() and effect().

🏏

Cricket analogy: The shift from @Input() to input() is like a cricket board moving from a paper team sheet a manager could quietly scribble changes onto, to a tamper-evident digital lineup that locks once submitted and can only be read, not silently altered mid-innings.

input() and input.required()

Where @Input() decorates a mutable class property, input() is called as a class field initializer that returns a read-only Signal<T>. You read the current value by calling it as a function, e.g. this.title(), rather than accessing it directly. input() accepts an optional default value and options object (alias, transform); input.required<T>() omits the default and instead requires the parent to bind it, enforced at compile time exactly like @Input({ required: true }) but as the primary, more concise spelling. Because the returned value is a signal, it composes directly with computed() to derive values and automatically participates in Angular's fine-grained, signal-based change detection without needing OnPush boilerplate reasoning.

🏏

Cricket analogy: Calling this.title() to read a signal input is like a scorer having to actually check today's official scorecard rather than assuming yesterday's total, since input.required<T>() is like a match that cannot start until the toss result is formally recorded.

typescript
@Component({
  selector: 'app-price-tag',
  standalone: true,
  template: `
    <span class="price">{{ formatted() }}</span>
  `
})
export class PriceTagComponent {
  amount = input.required<number>();
  currency = input<string>('USD');

  formatted = computed(() =>
    new Intl.NumberFormat('en-US', { style: 'currency', currency: this.currency() })
      .format(this.amount())
  );
}

model() for Two-Way Binding

model() creates a signal that is both readable and writable from inside the component, and automatically generates a paired output (named propertyNameChange) so the parent can use Angular's banana-in-a-box [(syntax))="parentSignal" for two-way binding — the same sugar that powers [(ngModel)]. Calling .set() or .update() on a model signal from within the child both updates the local value and emits the change event, so the parent's bound signal or property stays in sync without you manually wiring an @Output(). This collapses what previously required a paired @Input() + @Output() with a manually-emitted change event into a single declaration.

🏏

Cricket analogy: model() is like a shared scoreboard that both the scorer and the broadcaster can update, where the scorer changing the run tally automatically pushes the update out to the broadcast feed without a separate manual radio call.

typescript
@Component({
  selector: 'app-quantity-stepper',
  standalone: true,
  template: `
    <button (click)="decrement()">-</button>
    <span>{{ quantity() }}</span>
    <button (click)="increment()">+</button>
  `
})
export class QuantityStepperComponent {
  quantity = model<number>(1);

  increment(): void {
    this.quantity.update(q => q + 1);
  }
  decrement(): void {
    this.quantity.update(q => Math.max(0, q - 1));
  }
}

// Parent template:
// <app-quantity-stepper [(quantity)]="cartQty" />

model() is conceptually close to Vue's defineModel() macro or a controlled/uncontrolled hybrid in React with a value + onChange pair — but Angular generates the paired 'change' output for you automatically, so [(quantity)] in the parent template desugars to [quantity]="cartQty" (quantityChange)="cartQty = $event", exactly mirroring how [(ngModel)] itself works.

A signal returned by input() is read-only from inside the component that declares it — calling .set() on a plain input() signal is a compile error. Only model() signals are writable locally. If you need to derive local mutable state seeded from an input, create a separate local signal initialized from the input's value (often inside an effect() or constructor), rather than trying to mutate the input signal itself.

Because input(), input.required(), and model() are functions rather than decorators, they can be used in any class field context without needing experimental decorator support enabled, and they read naturally alongside signal(), computed(), and effect(). Angular's migration schematics (ng generate @angular/core:signal-input-migration) can automatically convert existing @Input() properties to input() across a codebase.

🏏

Cricket analogy: Migration schematics converting @Input() to input() automatically across a codebase is like a cricket board retrofitting every stadium's old manual scoreboard to a new digital system in one coordinated rollout rather than replacing each ground one at a time by hand.

  • input() and input.required() are function-based, signal-returning replacements for @Input(), read by calling the property as a function (e.g. title()).
  • Signal inputs are read-only from within the component; mutating them requires deriving a separate local signal.
  • model() creates a writable, two-way-bindable signal and automatically generates a matching '<name>Change' output.
  • [(propName)]="parentSignal" on a model() input desugars to the same property+event binding pattern as [(ngModel)].
  • Signal inputs compose naturally with computed() and effect(), integrating tightly with Angular's fine-grained reactivity model.
  • Angular provides automated migration schematics to convert existing @Input()/@Output() pairs to the signal-based equivalents.

Practice what you learned

Was this page helpful?

Topics covered

#TypeScript#AngularStudyNotes#WebDevelopment#SignalInputsAndModel#Signal#Inputs#Model#Input#StudyNotes#SkillVeris