AngularJS@Input & @Output

@Input and @Output in Angular

@Input and @Output are the primary mechanism for parent-child component communication in Angular. @Input lets a parent pass data down to a child; @Output lets a child send events up to the parent. Together they form the component API.

@Input — Passing Data Down

Decorate a child component property with @Input() to allow the parent to bind to it. The parent uses property binding [inputName]="value" to supply the value.

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

interface User {
  id: number;
  name: string;
  avatar: string;
  role: string;
}

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `
    <div class="card">
      <img [src]="user.avatar" [alt]="user.name" />
      <h3>{{ user.name }}</h3>
      <span class="badge">{{ user.role }}</span>
    </div>
  `,
})
export class UserCardComponent {
  @Input() user!: User;        // required input
  @Input() compact = false;    // optional with default
}

TS
// parent component
import { Component } from '@angular/core';
import { UserCardComponent } from './user-card.component';

@Component({
  selector: 'app-team',
  standalone: true,
  imports: [UserCardComponent],
  template: `
    @for (member of team; track member.id) {
      <app-user-card [user]="member" [compact]="true" />
    }
  `,
})
export class TeamComponent {
  team = [
    { id: 1, name: 'Alice', avatar: '/img/alice.jpg', role: 'Developer' },
    { id: 2, name: 'Bob',   avatar: '/img/bob.jpg',   role: 'Designer' },
  ];
}
Required Inputs (Angular 16+)

From Angular 16 you can mark an input as required. Angular will throw a compile-time error if the parent forgets to provide it — no more runtime crashes from undefined inputs.

TS
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-button',
  standalone: true,
  template: `<button [class]="variant">{{ label }}</button>`,
})
export class ButtonComponent {
  @Input({ required: true }) label!: string;   // must be provided
  @Input() variant = 'primary';                // optional, has default
}
Note
The \`required: true\` option was introduced in Angular 16. For Angular 15 and earlier, use the \`!\` non-null assertion and document the requirement in a comment.
Input Aliases

You can expose an input under a different public name than the internal property name using an alias.

TS
@Component({ selector: 'app-item', standalone: true, template: '' })
export class ItemComponent {
  // Public API uses 'itemData', internally stored as 'data'
  @Input('itemData') data!: { id: number; title: string };
}

HTML
<!-- Parent uses the alias -->
<app-item [itemData]="selectedItem" />
Input Transform (Angular 16.1+)

The transform option lets you coerce or convert the value before it is assigned to the property. A common use case is accepting a boolean-like string attribute.

TS
import { Component, Input, booleanAttribute, numberAttribute } from '@angular/core';

@Component({
  selector: 'app-chip',
  standalone: true,
  template: `<span [class.dismissible]="dismissible">{{ label }}</span>`,
})
export class ChipComponent {
  @Input({ required: true }) label!: string;

  // Accepts: [dismissible]="true", [dismissible]="false", or just the attribute presence
  @Input({ transform: booleanAttribute }) dismissible = false;

  // Coerces string "42" → number 42
  @Input({ transform: numberAttribute }) maxLength = 100;
}

HTML
<!-- These three are now equivalent -->
<app-chip label="Angular" [dismissible]="true" />
<app-chip label="Angular" dismissible="true" />
<app-chip label="Angular" dismissible />
@Output — Emitting Events Up

@Output() combined with EventEmitter lets a child component notify its parent that something happened. The parent listens using event binding (eventName)="handler($event)".

TS
// rating.component.ts (child)
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-rating',
  standalone: true,
  template: `
    @for (star of stars; track star) {
      <button
        (click)="select(star)"
        [class.filled]="star <= current"
      >★</button>
    }
  `,
})
export class RatingComponent {
  @Input()  current = 0;
  @Output() ratingChange = new EventEmitter<number>();

  stars = [1, 2, 3, 4, 5];

  select(value: number) {
    this.ratingChange.emit(value);
  }
}

TS
// parent component
import { Component } from '@angular/core';
import { RatingComponent } from './rating.component';

@Component({
  selector: 'app-product',
  standalone: true,
  imports: [RatingComponent],
  template: `
    <h2>{{ product.name }}</h2>
    <app-rating [current]="product.rating" (ratingChange)="onRate($event)" />
    <p>Your rating: {{ product.rating }} / 5</p>
  `,
})
export class ProductComponent {
  product = { name: 'Angular Course', rating: 4 };

  onRate(value: number) {
    this.product.rating = value;
    console.log('Saved rating:', value);
  }
}
Output Aliases

Like @Input, @Output supports aliases for separating the public event name from the internal property name.

TS
export class SearchComponent {
  // Public event is named 'search', internally it's the 'searchEvent' property
  @Output('search') searchEvent = new EventEmitter<string>();

  emit(query: string) {
    this.searchEvent.emit(query);
  }
}

HTML
<!-- Parent uses the alias -->
<app-search (search)="onSearch($event)" />
Signal-Based Inputs and Outputs (Angular 17.1+)

Angular 17.1 introduced input() and output() functions as the modern, signal-based alternative to @Input and @Output decorators. They provide better type safety and integrate with Angular's Signals reactivity system.

TS
import { Component, input, output, computed } from '@angular/core';

@Component({
  selector: 'app-product-card',
  standalone: true,
  template: `
    <div [class.featured]="featured()">
      <h3>{{ title() }}</h3>
      <p>{{ formattedPrice() }}</p>
      <button (click)="addToCart()">Add to Cart</button>
    </div>
  `,
})
export class ProductCardComponent {
  // input() — required
  title = input.required<string>();

  // input() — optional with default
  price = input(0);
  featured = input(false);

  // Computed from input signal
  formattedPrice = computed(() =>
    new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
      .format(this.price())
  );

  // output() — replaces @Output + EventEmitter
  addedToCart = output<{ title: string }>();

  addToCart() {
    this.addedToCart.emit({ title: this.title() });
  }
}
Tip
Prefer \`input()\` / \`output()\` for new Angular 17.1+ code. They offer better TypeScript inference, work naturally with computed signals, and do not require \`new EventEmitter()\`.
Comparison: Decorator vs Signal API

Feature

@Input / @Output

input() / output() (17.1+)

Import

@angular/core decorators

input, output functions

Type inference

Manual with !

Automatic from generics

Reactivity

Zone.js / OnPush

Fine-grained signal tracking

Required inputs

@Input({ required: true })

input.required<Type>()

Default value

@Input() prop = default

input(defaultValue)

Alias

@Input("alias")

input({ alias: "name" })

Transform

@Input({ transform })

input({ transform })

Event emitter

new EventEmitter<T>()

output<T>()

Complete Example: Shopping Cart Item

TS
// cart-item.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

export interface CartItem {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

@Component({
  selector: 'app-cart-item',
  standalone: true,
  template: `
    <div class="cart-item">
      <span class="name">{{ item.name }}</span>
      <span class="price">{{ item.price | currency }}</span>
      <div class="qty-controls">
        <button (click)="changeQty(-1)" [disabled]="item.quantity <= 1">-</button>
        <span>{{ item.quantity }}</span>
        <button (click)="changeQty(1)">+</button>
      </div>
      <span class="subtotal">{{ item.price * item.quantity | currency }}</span>
      <button class="remove" (click)="remove()">Remove</button>
    </div>
  `,
})
export class CartItemComponent {
  @Input({ required: true }) item!: CartItem;

  @Output() quantityChanged = new EventEmitter<{ id: number; quantity: number }>();
  @Output() removed = new EventEmitter<number>();

  changeQty(delta: number) {
    const newQty = this.item.quantity + delta;
    if (newQty >= 1) {
      this.quantityChanged.emit({ id: this.item.id, quantity: newQty });
    }
  }

  remove() {
    this.removed.emit(this.item.id);
  }
}

TS
// cart.component.ts (parent)
@Component({
  selector: 'app-cart',
  standalone: true,
  imports: [CartItemComponent, CurrencyPipe],
  template: `
    @for (item of items; track item.id) {
      <app-cart-item
        [item]="item"
        (quantityChanged)="updateQty($event)"
        (removed)="removeItem($event)"
      />
    }
    <p>Total: {{ total | currency }}</p>
  `,
})
export class CartComponent {
  items: CartItem[] = [
    { id: 1, name: 'Laptop Stand', price: 49.99, quantity: 1 },
    { id: 2, name: 'Keyboard',     price: 89.99, quantity: 2 },
  ];

  get total() {
    return this.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
  }

  updateQty(event: { id: number; quantity: number }) {
    const item = this.items.find(i => i.id === event.id);
    if (item) item.quantity = event.quantity;
  }

  removeItem(id: number) {
    this.items = this.items.filter(i => i.id !== id);
  }
}
Summary
  • @Input() — marks a property as receivable from a parent via [property]="value".

  • @Input({ required: true }) — Angular 16+ compile-time enforcement.

  • @Input({ transform }) — coerce input values (booleanAttribute, numberAttribute).

  • @Output() + EventEmitter<T> — emit typed events to the parent.

  • Parent listens with (outputName)="handler($event)".

  • Signal API: input() / input.required<T>() and output<T>() are the modern Angular 17.1+ equivalents.

  • @Input alias and @Output alias decouple public API names from internal property names.