input()– for receiving values (read-only),model()– for two-way bound values (read-write),@Output()– for emitting events (classic Angular pattern).
Here’s a detailed comparison and explanation of input(), model(), and @Output() in Angular, including how they relate to each other, their use cases, component attribute usage, and examples.
🔹 1. input() — One-way Input (Read-Only)
Purpose:
Used to receive data from a parent component. It is a read-only signal.
Syntax:
import { input } from '@angular/core';
@Component({ ... })
export class CustomSlider {
  value = input(0); // default value 0
}
Template usage:
<custom-slider [value]="50"></custom-slider>
Notes:
- Returns an 
InputSignal<T>. - Use 
value()to access the actual value. - Cannot be updated from the component itself.
 
Use case:
Any value passed from parent to child and not changed by the child.
🔹 2. model() — Two-way Binding Input (Read + Write)
Purpose:
Used to receive a value from a parent and propagate changes back to the parent. It’s a writable signal.
Syntax:
import { model } from '@angular/core';
@Component({ ... })
export class CustomSlider {
  value = model(0);
  increment() {
    this.value.update(v => v + 10); // Update value and propagate
  }
}
Template usage (parent):
<custom-slider [(value)]="volume"></custom-slider>
Notes:
model()returns a writable signal.- Angular automatically creates an implicit output named 
<property>Change(e.g.,valueChange). - Use 
.set()or.update()to modify the value and notify the parent. - Two-way binding uses 
[(property)]="signalInstance"syntax. 
Use case:
Custom form controls, sliders, checkboxes — any input that modifies data internally and must reflect back to the parent.
🔹 3. @Output() — Event Emission (Old-style)
Purpose:
Used to emit events from child to parent manually.
Syntax:
import { Output, EventEmitter } from '@angular/core';
@Component({ ... })
export class CustomSlider {
  @Output() valueChange = new EventEmitter<number>();
  increment() {
    this.valueChange.emit(10);
  }
}
Template usage (parent):
<custom-slider (valueChange)="onValueChanged($event)"></custom-slider>
Notes:
- This is the traditional Angular approach.
 - You manage the event manually and emit values explicitly.
 - Used often in legacy code and still valid.
 
🔄 Comparison Table
| Feature | input() | model() | @Output() | 
|---|---|---|---|
| Direction | Parent → Child | Two-way: Parent ⇄ Child | Child → Parent (event-based) | 
| Writable by child | ❌ No | ✅ Yes | ✅ Yes | 
| Signal-based | ✅ Yes | ✅ Yes | ❌ No | 
| Implicit output | ❌ No | ✅ Yes (<prop>Change) | ❌ No (you define manually) | 
| Two-way binding | ❌ No | ✅ Yes | ❌ No (manual binding) | 
| Decorator usage | ❌ No | ❌ No | ✅ Yes (@Output) | 
| Template binding | [value]="x" | [(value)]="x" | (valueChange)="handler($event)" | 
🔧 Advanced Options
Aliasing:
value = input(0, { alias: 'sliderValue' });
<custom-slider [sliderValue]="x" />
Required Inputs:
value = input.required<number>();
// must be set in the parent template
Transforms:
import { booleanAttribute } from '@angular/core';
disabled = input(false, { transform: booleanAttribute });
✅ When to Use What?
| Scenario | Use | 
|---|---|
| You want to accept data from the parent but not modify it | input() | 
| You want the component to modify the value and reflect the change to the parent | model() | 
| You want to emit custom events for things like clicks, status changes | @Output() | 
🧪 Quick Example
volume-slider.component.ts
@Component({
  selector: 'volume-slider',
  template: `<button (click)="increment()">+</button>`
})
export class VolumeSlider {
  value = model(0); // Two-way bound
  increment() {
    this.value.update(v => v + 5);
  }
}
media-player.component.ts
@Component({
  template: `<volume-slider [(value)]="volume" />`
})
export class MediaPlayer {
  volume = signal(10); // signal passed as model
}
Angular allows specifying inherited @Input() and @Output() bindings through the inputs and outputs metadata arrays in a component, even if the base class doesn't declare them in the derived class explicitly.
Here's a focused example demonstrating that:
✅ Inheriting @Input() and Aliasing It
🔹 Base Component (with @Input() and @Output())
// base-slider.ts
import { Input, Output, EventEmitter } from '@angular/core';
export class BaseSlider {
  @Input() disabled: boolean = false;
  @Output() valueChanged = new EventEmitter<number>();
}
🔹 Derived Component with Aliased @Input() and @Output()
// custom-slider.component.ts
import { Component } from '@angular/core';
import { BaseSlider } from './base-slider';
@Component({
  selector: 'app-custom-slider',
  template: `
    <input
      type="range"
      [disabled]="disabled"
      (input)="onInput($event)"
    />
  `,
  // Aliasing inherited Input and Output names
  inputs: ['disabled: sliderDisabled'],
  outputs: ['valueChanged: onSliderChange']
})
export class CustomSliderComponent extends BaseSlider {
  onInput(event: Event) {
    const value = +(event.target as HTMLInputElement).value;
    this.valueChanged.emit(value);
  }
}
🔹 Usage in a Parent Component
<!-- parent.component.html -->
<app-custom-slider
  [sliderDisabled]="true"
  (onSliderChange)="handleSliderChange($event)">
</app-custom-slider>
// parent.component.ts
handleSliderChange(value: number) {
  console.log('Slider changed to:', value);
}
✅ Key Takeaways
inputs: ['disabled: sliderDisabled'] maps the inherited disabled input to the alias sliderDisabled.outputs: ['valueChanged: onSliderChange'] maps the inherited output to the alias onSliderChange.- This enables cleaner public APIs for your components while reusing base class logic.