← Back to Article List         
Unlock Angular Component Communication with @Input()

Unlock Angular Component Communication with @Input()

Published on 22 Sep 2026     9 min read Angular Tutorial
Property Decorators

What is a Property Decorator in Angular?

A property decorator is applied to a property of an Angular component, directive, or service class. It provides additional metadata to Angular and tells Angular to treat that property in a special way.

A property decorator begins with @ and is placed immediately above the property.

@DecoratorName()
propertyName: datatype;

Example:

@Input()
productName = '';

Here:

  • productName is a normal TypeScript class property.

  • @Input() is the property decorator.

  • The decorator tells Angular that the property can receive a value from a parent component.

Common Angular property decorators

Decorator Purpose
@Input() Receives data from a parent component
@Output() Sends events/data to a parent component
@ViewChild() Accesses one child element/component from the component view
@ViewChildren() Accesses multiple child elements/components
@ContentChild() Accesses projected content
@ContentChildren() Accesses multiple projected-content elements

What is @Input()?

@Input() is an Angular property decorator that allows a child component or directive to receive data from its parent component.

The data direction is:

Parent Component → Child Component

@Input() makes a child property available for property binding in the parent template.

Angular officially describes @Input() as a decorator that marks a class field as an input property. Angular automatically updates that field during change detection. (Angular)

Basic syntax

import { Input } from '@angular/core';

@Input() propertyName: datatype;

Example:

@Input() userName = '';

The parent can now pass a value to it:

<app-user [userName]="name"></app-user>

Complete parent-to-child example

Suppose the parent has product information and needs to display it using a reusable child component.

1. Create a model

product.model.ts

export interface Product {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
}

2. Child component

product-card.ts

import { Component, Input } from '@angular/core';
import { CurrencyPipe } from '@angular/common';
import { Product } from './product.model';

@Component({
  selector: 'app-product-card',
  standalone: true,
  imports: [CurrencyPipe],
  templateUrl: './product-card.html',
  styleUrl: './product-card.css'
})
export class ProductCard {

  @Input({ required: true })
  product!: Product;

  @Input()
  buttonText = 'View Product';
}

Explanation

@Input({ required: true })
product!: Product;
  • @Input() exposes the property to the parent.

  • required: true means the parent must provide it.

  • product is the child component property.

  • Product provides type safety.

  • ! is TypeScript’s definite-assignment assertion.

  • It tells TypeScript that Angular will assign the value.

The second input has a default value:

@Input()
buttonText = 'View Product';

If the parent does not pass buttonText, the child uses "View Product".

3. Child template

product-card.html

<div class="product-card">
  <h2>{{ product.name }}</h2>

  <p>Product ID: {{ product.id }}</p>

  <p>Price: {{ product.price | currency:'INR' }}</p>

  @if (product.inStock) {
    <p class="available">Available</p>
  } @else {
    <p class="unavailable">Out of stock</p>
  }

  <button>{{ buttonText }}</button>
</div>

The child directly reads the properties received from the parent:

{{ product.name }}
{{ product.price }}

4. Parent component

app.ts

import { Component } from '@angular/core';
import { ProductCard } from './product-card';
import { Product } from './product.model';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ProductCard],
  templateUrl: './app.html'
})
export class App {

  selectedProduct: Product = {
    id: 101,
    name: 'Wireless Keyboard',
    price: 2499,
    inStock: true
  };

  actionText = 'Buy Now';

  changeProduct(): void {
    this.selectedProduct = {
      id: 102,
      name: 'Wireless Mouse',
      price: 1299,
      inStock: false
    };
  }
}

Because this is a standalone Angular component, the child component must be added to the parent's imports:

imports: [ProductCard]

5. Parent template

app.html

<h1>Product Store</h1>

<app-product-card
  [product]="selectedProduct"
  [buttonText]="actionText">
</app-product-card>

<button (click)="changeProduct()">
  Change Product
</button>

Understanding the binding

[product]="selectedProduct"
Part Meaning
[product] Input property in the child
selectedProduct Property in the parent
= Passes the parent value to the child

Angular effectively performs:

child.product = parent.selectedProduct;

When selectedProduct changes, Angular updates the child input during change detection.


Static value versus property binding

These two examples are different.

Static string

<app-product-card buttonText="Buy Now"></app-product-card>

Angular passes the literal string:

Buy Now

Property binding

<app-product-card [buttonText]="actionText"></app-product-card>

Angular evaluates the parent property:

actionText = 'Buy Now';

Important number example

Without brackets:

<app-counter count="10"></app-counter>

"10" is normally passed as a string.

With brackets:

<app-counter [count]="10"></app-counter>

10 is passed as a number.

@Input() count = 0;

Optional and required inputs

Optional input with a default value

@Input()
title = 'Default Title';

Usage is optional:

<app-card></app-card>

If the parent does not pass a title, "Default Title" is used.

Required input

@Input({ required: true })
title!: string;

The parent must pass the property:

<app-card [title]="pageTitle"></app-card>

If a required input is missing, Angular reports a build-time/template compilation error. (Angular)


Receiving different data types

@Input() can receive primitive values, arrays, objects, and functions.

String

@Input() title = '';
<app-card title="Angular Tutorial"></app-card>

Number

@Input() count = 0;
<app-card [count]="25"></app-card>

Boolean

@Input() isActive = false;
<app-card [isActive]="true"></app-card>

Array

@Input() skills: string[] = [];
technicalSkills = ['C#', 'ASP.NET Core', 'Angular'];
<app-skills [skills]="technicalSkills"></app-skills>

Object

@Input({ required: true })
product!: Product;
<app-product-card [product]="selectedProduct"></app-product-card>

Input alias

An alias allows the parent template to use a different input name from the child’s TypeScript property.

Child

@Input({ alias: 'productTitle' })
title = '';

Parent template

<app-product-card
  productTitle="Angular Course">
</app-product-card>

Inside the child class, the property is still accessed as:

this.title

It is not accessed as:

this.productTitle

The shorter legacy syntax is also supported:

@Input('productTitle')
title = '';

Aliases can help avoid native DOM-property name collisions or preserve compatibility while renaming an input, but unnecessary aliases may make the code confusing. (Angular)


Input transformations

A transform converts an incoming value before Angular assigns it to the child property.

Angular provides built-in transformations for boolean and number values.

Boolean transformation

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

@Component({
  selector: 'app-button',
  standalone: true,
  template: `
    <button [disabled]="disabled">
      Save
    </button>
  `
})
export class Button {

  @Input({ transform: booleanAttribute })
  disabled = false;
}

Parent:

<app-button disabled></app-button>

The presence of disabled is converted to:

true

The literal value "false" is converted to false.

Number transformation

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

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `<p>Count: {{ count }}</p>`
})
export class Counter {

  @Input({ transform: numberAttribute })
  count = 0;
}

Parent:

<app-counter count="25"></app-counter>

Although "25" comes from an HTML attribute, numberAttribute converts it to the number 25.

Custom transformation

function trimText(value: string | undefined): string {
  return value?.trim() ?? '';
}

export class UserCard {

  @Input({ transform: trimText })
  userName = '';
}

Parent:

<app-user-card
  userName="   Syed Mohamed   ">
</app-user-card>

Child receives:

Syed Mohamed

Angular recommends transformations when an input needs value normalization. (Angular)


Detecting input changes using ngOnChanges

When the parent changes an input value, the child can respond using the ngOnChanges lifecycle hook.

Child component

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

@Component({
  selector: 'app-user',
  standalone: true,
  template: `<h2>{{ userName }}</h2>`
})
export class User implements OnChanges {

  @Input()
  userName = '';

  ngOnChanges(changes: SimpleChanges): void {
    const userNameChange = changes['userName'];

    if (userNameChange) {
      console.log(
        'Previous value:',
        userNameChange.previousValue
      );

      console.log(
        'Current value:',
        userNameChange.currentValue
      );

      console.log(
        'First change:',
        userNameChange.firstChange
      );
    }
  }
}

SimpleChange provides:

Property Meaning
previousValue Previous input value
currentValue New input value
firstChange Whether this is the first assignment
isFirstChange() Method that checks the first assignment

ngOnChanges() runs:

  1. When Angular initially assigns the input.

  2. Whenever Angular detects that its bound value has changed.

For the initial component creation, ngOnChanges() runs before ngOnInit().


Using an input setter

An input can also be implemented using a setter.

export class UserCard {

  private normalizedName = '';

  @Input()
  set userName(value: string) {
    this.normalizedName = value.trim().toUpperCase();
  }

  get userName(): string {
    return this.normalizedName;
  }
}

Parent:

<app-user-card
  [userName]="'Syed Mohamed'">
</app-user-card>

Result:

SYED MOHAMED

However:

  • Keep input setters simple.

  • Do not perform expensive API calls or complex DOM work in a setter.

  • Prefer an input transform when the requirement is only value conversion.

  • Angular may call the setter multiple times. (Angular)


How change detection affects @Input()

Consider:

<app-product-card
  [product]="selectedProduct">
</app-product-card>

When Angular runs change detection:

  1. Angular evaluates selectedProduct in the parent.

  2. It passes the current value to the child's product input.

  3. The child template renders the received product.

  4. When the parent value changes, Angular updates the child.

  5. If appropriate, the child’s ngOnChanges() is called.

Object reference consideration

This creates a new object reference:

this.selectedProduct = {
  ...this.selectedProduct,
  price: 3000
};

This only mutates the existing object:

this.selectedProduct.price = 3000;

Creating a new object is usually safer, especially when using OnPush change detection, because Angular can clearly detect that the input reference changed.


Can the child modify an input?

Technically, this is possible:

this.product.name = 'Changed';

But it is generally poor design because the parent owns the data.

A better communication pattern is:

Parent → Child: @Input()
Child → Parent: @Output()

Use @Input() to receive data. Use an output event to ask the parent to update it.

Do not treat @Input() as two-way binding automatically.


Classic @Input() versus modern signal input

Modern Angular supports two approaches.

Decorator-based input

@Input()
title = '';

Template:

<h2>{{ title }}</h2>

Signal-based input

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

export class ProductCard {
  title = input('');
}

Template:

<h2>{{ title() }}</h2>

Required signal input:

title = input.required<string>();

Parent usage is the same:

<app-product-card
  [title]="pageTitle">
</app-product-card>

The Angular team currently recommends signal-based input() for new projects, but the traditional @Input() decorator remains fully supported. Signal inputs are read-only signals and are accessed by calling them as functions. (Angular)

Feature @Input() input()
Type Decorated class property Read-only input signal
Read value this.title this.title()
Template {{ title }} {{ title() }}
Required @Input({required: true}) input.required<T>()
Existing projects Very common Increasingly common
New Angular projects Supported Recommended

Important points

  • @Input() is a property decorator.

  • Import it from @angular/core.

  • It passes data from a parent to a child.

  • The child must declare the receiving property with @Input().

  • The parent normally passes dynamic data using property binding: [inputName]="value".

  • Without square brackets, the value is generally treated as a literal string.

  • Inputs can receive strings, numbers, booleans, objects and arrays.

  • Input names are case-sensitive.

  • Use { required: true } for mandatory data.

  • Use alias to expose a different template binding name.

  • Use transform to normalize incoming values.

  • Use ngOnChanges() when the child must react to changes.

  • Avoid directly modifying parent-owned objects inside the child.

  • @Input() provides one-way parent-to-child communication.

  • The signal-based input() API is recommended for new Angular development, while @Input() remains supported.