Skip to main content

Overview

Angular provides two primary mechanisms for component communication: inputs for passing data from parent to child, and outputs for emitting events from child to parent.

@Input Decorator

Signature

@Input(options?: string | Input): PropertyDecorator

Parameters

options
string | Input
Either an alias string or a configuration object.

Input Options

alias
string
Alternative name for the input in templates.
@Input({ alias: 'userName' }) name!: string;
required
boolean
Whether the input must be provided. Causes compile error if missing.
@Input({ required: true }) userId!: number;
transform
(value: any) => any
Function to transform the input value before assignment.
@Input({ transform: booleanAttribute }) disabled: boolean = false;

Basic Input Example

user-card.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `
    <div class="card">
      <h3>{{name}}</h3>
      <p>{{email}}</p>
      <span class="badge">{{role}}</span>
    </div>
  `
})
export class UserCardComponent {
  @Input() name: string = '';
  @Input() email: string = '';
  @Input() role: string = 'user';
}
Usage:
<app-user-card 
  name="John Doe" 
  email="john@example.com" 
  role="admin">
</app-user-card>

Input with Alias

product.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-product',
  standalone: true,
  template: `
    <div>
      <h4>{{productName}}</h4>
      <p>Price: ${{productPrice}}</p>
    </div>
  `
})
export class ProductComponent {
  @Input({ alias: 'name' }) productName!: string;
  @Input({ alias: 'price' }) productPrice!: number;
}
Usage:
<app-product name="Laptop" [price]="999"></app-product>

Required Inputs

profile.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-profile',
  standalone: true,
  template: `
    <div>
      <h2>{{userId}}</h2>
      <p>{{bio || 'No bio available'}}</p>
    </div>
  `
})
export class ProfileComponent {
  // Compile error if not provided
  @Input({ required: true }) userId!: string;
  
  // Optional input
  @Input() bio?: string;
}

Input Transforms

toggle.component.ts
import { Component, Input, booleanAttribute, numberAttribute } from '@angular/core';

@Component({
  selector: 'app-toggle',
  standalone: true,
  template: `
    <button 
      [disabled]="disabled" 
      [style.width.px]="width">
      {{label}}
    </button>
  `
})
export class ToggleComponent {
  @Input({ transform: booleanAttribute }) disabled = false;
  @Input({ transform: numberAttribute }) width = 100;
  @Input() label = 'Toggle';
}
Usage:
<!-- String "true" converted to boolean true -->
<app-toggle disabled="true" width="200"></app-toggle>

<!-- Boolean attribute converted to true -->
<app-toggle disabled></app-toggle>

input() Function (Signal-Based)

Signature

input<T>(initialValue?: T, options?: InputOptions<T>): InputSignal<T>
input.required<T>(options?: InputOptions<T>): InputSignal<T>

Signal Input Example

counter.component.ts
import { Component, input, computed } from '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <div>
      <p>Count: {{count()}}</p>
      <p>Double: {{doubled()}}</p>
      <p>Label: {{label()}}</p>
    </div>
  `
})
export class CounterComponent {
  // Optional with default
  count = input(0);
  
  // Optional without default
  label = input<string>();
  
  // Derived value
  doubled = computed(() => this.count() * 2);
}
Usage:
<app-counter [count]="5" label="Items"></app-counter>

Required Signal Input

item-detail.component.ts
import { Component, input } from '@angular/core';

interface Item {
  id: number;
  name: string;
  description: string;
}

@Component({
  selector: 'app-item-detail',
  standalone: true,
  template: `
    <div>
      <h3>{{item().name}}</h3>
      <p>{{item().description}}</p>
    </div>
  `
})
export class ItemDetailComponent {
  // Required signal input
  item = input.required<Item>();
}

Signal Input with Transform

date-display.component.ts
import { Component, input } from '@angular/core';

@Component({
  selector: 'app-date-display',
  standalone: true,
  template: `
    <div>Date: {{formattedDate()}}</div>
  `
})
export class DateDisplayComponent {
  date = input('', {
    transform: (value: string | Date) => {
      if (typeof value === 'string') {
        return new Date(value);
      }
      return value;
    }
  });
  
  formattedDate = computed(() => {
    const d = this.date();
    return d ? d.toLocaleDateString() : '';
  });
}

@Output Decorator

Signature

@Output(alias?: string): PropertyDecorator

Parameters

alias
string
Alternative name for the output in templates.

Basic Output Example

button.component.ts
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-button',
  standalone: true,
  template: `
    <button (click)="handleClick()">
      {{label}}
    </button>
  `
})
export class ButtonComponent {
  @Output() clicked = new EventEmitter<void>();
  label = 'Click Me';

  handleClick() {
    this.clicked.emit();
  }
}
Usage:
<app-button (clicked)="onButtonClick()"></app-button>

Output with Data

search.component.ts
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-search',
  standalone: true,
  template: `
    <input 
      #input
      type="text" 
      (input)="onSearch(input.value)">
  `
})
export class SearchComponent {
  @Output() search = new EventEmitter<string>();

  onSearch(query: string) {
    this.search.emit(query);
  }
}
Usage:
<app-search (search)="handleSearch($event)"></app-search>
handleSearch(query: string) {
  console.log('Search query:', query);
}

Output with Alias

dropdown.component.ts
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-dropdown',
  standalone: true,
  template: `
    <select (change)="onValueChange($event)">
      <option value="1">Option 1</option>
      <option value="2">Option 2</option>
    </select>
  `
})
export class DropdownComponent {
  @Output('valueChange') valueChanged = new EventEmitter<string>();

  onValueChange(event: Event) {
    const value = (event.target as HTMLSelectElement).value;
    this.valueChanged.emit(value);
  }
}
Usage:
<app-dropdown (valueChange)="onDropdownChange($event)"></app-dropdown>

output() Function (Signal-Based)

Signature

output<T = void>(options?: OutputOptions): OutputEmitterRef<T>

Signal Output Example

modern-button.component.ts
import { Component, input, output } from '@angular/core';

@Component({
  selector: 'app-modern-button',
  standalone: true,
  template: `
    <button (click)="handleClick()">
      {{label()}}
    </button>
  `
})
export class ModernButtonComponent {
  label = input('Click');
  clicked = output<void>();

  handleClick() {
    this.clicked.emit();
  }
}

Output with Custom Event Type

item-list.component.ts
import { Component, output } from '@angular/core';

interface ItemSelectedEvent {
  id: number;
  name: string;
  timestamp: Date;
}

@Component({
  selector: 'app-item-list',
  standalone: true,
  template: `
    <div *ngFor="let item of items" (click)="selectItem(item)">
      {{item.name}}
    </div>
  `
})
export class ItemListComponent {
  itemSelected = output<ItemSelectedEvent>();
  
  items = [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' }
  ];

  selectItem(item: {id: number, name: string}) {
    this.itemSelected.emit({
      ...item,
      timestamp: new Date()
    });
  }
}

Two-Way Binding

NgModel Pattern

custom-input.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-custom-input',
  standalone: true,
  template: `
    <input 
      [value]="value" 
      (input)="onValueChange($event)">
  `
})
export class CustomInputComponent {
  @Input() value: string = '';
  @Output() valueChange = new EventEmitter<string>();

  onValueChange(event: Event) {
    const newValue = (event.target as HTMLInputElement).value;
    this.valueChange.emit(newValue);
  }
}
Usage:
<app-custom-input [(value)]="username"></app-custom-input>

Signal-Based Two-Way Binding

signal-input.component.ts
import { Component, model } from '@angular/core';

@Component({
  selector: 'app-signal-input',
  standalone: true,
  template: `
    <input 
      [value]="value()" 
      (input)="value.set($any($event.target).value)">
  `
})
export class SignalInputComponent {
  value = model('');
}
Usage:
<app-signal-input [(value)]="username"></app-signal-input>

Parent-Child Communication Example

parent.component.ts
import { Component } from '@angular/core';
import { ChildComponent } from './child.component';

@Component({
  selector: 'app-parent',
  standalone: true,
  imports: [ChildComponent],
  template: `
    <div>
      <h2>Parent Component</h2>
      <p>Message from child: {{childMessage}}</p>
      
      <app-child
        [parentMessage]="parentMessage"
        (messageToParent)="receiveMessage($event)">
      </app-child>
    </div>
  `
})
export class ParentComponent {
  parentMessage = 'Hello from parent';
  childMessage = '';

  receiveMessage(message: string) {
    this.childMessage = message;
  }
}
child.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child',
  standalone: true,
  template: `
    <div>
      <h3>Child Component</h3>
      <p>{{parentMessage}}</p>
      <button (click)="sendMessage()">Send to Parent</button>
    </div>
  `
})
export class ChildComponent {
  @Input() parentMessage: string = '';
  @Output() messageToParent = new EventEmitter<string>();

  sendMessage() {
    this.messageToParent.emit('Hello from child');
  }
}

Best Practices

  • Use signal inputs (input()) for new code - they’re more performant
  • Mark inputs as required when they’re essential for component function
  • Use input transforms for common conversions (boolean, number)
  • Prefer descriptive event names for outputs (e.g., userSelected over select)
  • Use TypeScript types for output events to ensure type safety
  • Don’t mutate input objects - create new objects instead
  • Always complete EventEmitters in ngOnDestroy if they’re long-lived
  • Avoid complex logic in input setters
  • Don’t emit outputs in ngOnInit or constructor

Comparison: Decorator vs Function

Feature@Input / @Outputinput() / output()
TypeDecoratorFunction
ReactivityManualSignal-based
Required inputsVia optionBuilt-in support
TransformVia optionVia option
Change detectionZone.jsSignals
PerformanceStandardBetter

See Also

Component Inputs

Learn about input properties

Component Outputs

Master output events

Signals

Understand signal reactivity

Two-Way Binding

Implement two-way binding