Component Communication

Read one Angular interview question at a time, then flip for the complete explanation, example, and code.

0/70 Known filtered set
Level
Read Full Guide Question: 1 of 70

Angular Interview Questions · Component Communication Companion Article

Angular Component Communication

Angular applications are built from many components. A small application may contain only a few components, while a real production application can contain hundreds of components arranged in parent-child trees. These components cannot work completely independently. They frequently need to exchange data, notify each other about user actions, or share application state. Angular provides several communication mechanisms for different situations: Inputs for sending data from a parent to a child Outputs for sending events from a child to a parent Model inputs for two-way component communication Component references for directly accessing a component instance Services for communication between components that do not have a simple parent-child relationship Signals for reactive state communication Choosing the correct communication mechanism is important because it affects readability, maintainability, reusability, and application architecture.

Understanding the Direction of Component Communication

Before choosing an Angular API, identify where the information starts and where it needs to go.

A typical component hierarchy may look like this:

Text
AppComponent
│
├── HeaderComponent
│
├── ProductListComponent
│   ├── ProductCardComponent
│   ├── ProductCardComponent
│   └── ProductCardComponent
│
└── CartComponent

Different communication requirements need different solutions.

For example:

Text
ProductListComponent
        |
        | product
        ↓
ProductCardComponent

The parent sends product information to the child.

This is normally handled using an input.

Now consider:

Text
ProductListComponent
        ↑
        | addToCart event
        |
ProductCardComponent

The child informs the parent that the user clicked Add to Cart.

This is normally handled using an output.

If multiple unrelated components need access to the cart state:

Text
ProductCardComponent
        |
        ↓
     CartService
        ↑
        |
CartComponent

A shared service can provide a cleaner solution.

Input Properties

An input property allows a component to receive information from its parent.

The general communication direction is:

Text
Parent → Child

Suppose a parent component contains a username:

TypeScript
export class ParentComponent {
  username = 'Rahul';
}

The child can declare an input:

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

@Component({
  selector: 'app-user',
  template: `<h2>{{ name }}</h2>`
})
export class UserComponent {
  @Input() name = '';
}

The parent passes its value using property binding:

HTML
<app-user [name]="username"></app-user>

Angular evaluates username in the parent and provides the resulting value to the child's name property.

Static Values vs Dynamic Values

A value without square brackets is generally treated as literal text:

HTML
<app-user name="Rahul"></app-user>

Property binding evaluates an Angular expression:

HTML
<app-user [name]="username"></app-user>

This distinction matters.

For example:

HTML
<app-product price="500"></app-product>

passes a text value.

While:

HTML
<app-product [price]="500"></app-product>

passes the evaluated numeric expression.

Passing Objects Through Inputs

Inputs are not limited to strings and numbers.

A complete object can be provided:

TypeScript
product = {
  id: 101,
  name: 'Laptop',
  price: 65000
};

Parent template:

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

Child:

TypeScript
@Input() product!: {
  id: number;
  name: string;
  price: number;
};

Passing structured objects is common when creating reusable UI components such as:

  • Product cards
  • User cards
  • Order rows
  • Dashboard widgets
  • Profile components
  • Table rows

Modern input() API

Modern Angular provides the input() function as an alternative to decorator-based @Input() declarations.

A component can declare:

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

@Component({
  selector: 'app-user',
  template: `<h2>{{ name() }}</h2>`
})
export class UserComponent {
  name = input('');
}

The parent still uses normal property binding:

HTML
<app-user [name]="username"></app-user>

The important difference is that input() produces a signal-based input.

Therefore, its value is read by calling it:

TypeScript
this.name()

instead of:

TypeScript
this.name

Angular's current input() API supports optional inputs and required inputs.

Required Inputs

Sometimes a child component cannot work correctly without a particular value.

For example, a product details component may require a product ID.

It can be declared as:

TypeScript
productId = input.required<number>();

Parent:

HTML
<app-product-details
  [productId]="selectedProductId"
></app-product-details>

Required inputs make the component contract clearer.

Instead of silently allowing an important value to be missing, the component explicitly states that the consumer must provide it.

Input Transformations

An input may sometimes need normalization before the component uses it.

For example, a component may receive configuration values that need conversion or transformation.

Keeping input normalization close to the input boundary can make the rest of the component simpler because its internal logic works with predictable values.

The general design principle is:

Text
External value
     ↓
Input boundary
     ↓
Validation / transformation
     ↓
Internal component logic

Output Events

Inputs solve parent-to-child communication.

For the opposite direction, Angular provides outputs.

The communication direction becomes:

Text
Child → Parent

The child does not normally modify the parent's state directly.

Instead, it announces that something happened.

Typical child events include:

Text
save
cancel
delete
selected
submitted
closed
quantityChanged
itemAdded

This event-based design keeps the child component reusable.

Traditional @Output() Communication

A traditional Angular output uses @Output() and EventEmitter.

Example:

TypeScript
import { Component, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'app-product-card',
  template: `
    <button (click)="addProduct()">
      Add to Cart
    </button>
  `
})
export class ProductCardComponent {
  @Output() addToCart = new EventEmitter<number>();

  addProduct(): void {
    this.addToCart.emit(101);
  }
}

Parent:

HTML
<app-product-card
  (addToCart)="handleAddToCart($event)">
</app-product-card>

Parent class:

TypeScript
handleAddToCart(productId: number): void {
  console.log('Product selected:', productId);
}

$event represents the value emitted by the child.

Modern output() API

Modern Angular also provides the output() function for declaring component outputs. Angular documents output() as the newer output API, and parents can listen to these outputs through normal template event bindings.

Example:

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

@Component({
  selector: 'app-product-card',
  template: `
    <button (click)="selectProduct()">
      Select
    </button>
  `
})
export class ProductCardComponent {
  selected = output<number>();

  selectProduct(): void {
    this.selected.emit(101);
  }
}

Parent:

HTML
<app-product-card
  (selected)="onProductSelected($event)">
</app-product-card>

Parent class:

TypeScript
onProductSelected(productId: number): void {
  console.log(productId);
}

The communication flow remains:

Text
User Action
    ↓
Child Component
    ↓
output.emit(value)
    ↓
Parent Event Binding
    ↓
Parent Handler

Outputs Should Represent Events

A useful naming convention is to describe what happened.

Good examples:

TypeScript
saved = output<void>();
productSelected = output<Product>();
quantityChanged = output<number>();
dialogClosed = output<void>();

Avoid designing an output like a command sent upward.

Components become easier to understand when outputs represent events rather than instructions to the parent.

Parent-to-Child Communication

Parent-to-child communication is one of the most common Angular patterns.

Consider an ecommerce application.

The parent has:

TypeScript
selectedProduct = {
  id: 10,
  name: 'Keyboard',
  price: 2500
};

The child declares:

TypeScript
product = input.required<Product>();

Parent:

HTML
<app-product-details
  [product]="selectedProduct">
</app-product-details>

Child:

HTML
<h2>{{ product().name }}</h2>
<p>₹{{ product().price }}</p>

The parent owns the data.

The child receives the data and decides how to display or use it.

This creates a predictable one-way flow:

Text
Parent State
     ↓
Input Binding
     ↓
Child Component
     ↓
Rendered UI

One-way flow is valuable because the owner of the state remains easy to identify.

Child-to-Parent Communication

Consider a product card containing an Add to Cart button.

The product card should not need to understand the complete shopping-cart implementation.

Instead, it emits an event.

Child:

TypeScript
productAdded = output<number>();

addToCart(): void {
  this.productAdded.emit(this.product().id);
}

Parent:

HTML
<app-product-card
  [product]="product"
  (productAdded)="addProductToCart($event)">
</app-product-card>

Parent:

TypeScript
addProductToCart(productId: number): void {
  // Update cart state
}

This produces good separation of responsibilities.

The child knows:

Text
"The user requested this product."

The parent decides:

Text
"What should happen when the product is requested?"

The same child could therefore be reused in different contexts.

Model Inputs

Sometimes communication needs to work in both directions.

For example:

Text
Parent → Child
Parent ← Child

A reusable counter might receive its current value from the parent but also modify that value when its buttons are clicked.

Modern Angular provides model inputs for this pattern. A model input is writable and Angular automatically creates a corresponding change output for it.

model()

A model input is declared using model().

Example:

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

@Component({
  selector: 'app-counter',
  template: `
    <button (click)="decrease()">-</button>
    <span>{{ count() }}</span>
    <button (click)="increase()">+</button>
  `
})
export class CounterComponent {
  count = model(0);

  increase(): void {
    this.count.update(value => value + 1);
  }

  decrease(): void {
    this.count.update(value => value - 1);

  }
}

The parent can use:

HTML
<app-counter [(count)]="quantity"></app-counter>

This creates two-way component binding.

Angular's model input automatically has a matching output whose name uses the Change suffix. For example, a count model corresponds to countChange.

Conceptually:

Text
count input
     +
countChange output
     =
[(count)]

Required Model Inputs

A model can also represent a value that the parent must provide.

For example:

TypeScript
checked = model.required<boolean>();

This is useful for reusable controls where a parent-owned value must always exist.

Angular demonstrates this pattern for two-way binding with model signals.

Two-Way Component Binding

Angular uses the [()] syntax for two-way binding.

It combines:

Text
[property]

with:

Text
(event)

giving:

Text
[(property)]

Angular documentation commonly refers to this syntax as banana-in-a-box.

Consider:

TypeScript
quantity = 1;

Parent template:

HTML
<app-quantity-selector
  [(quantity)]="quantity">
</app-quantity-selector>

<p>Selected quantity: {{ quantity }}</p>

Child:

TypeScript
quantity = model(1);

When the child updates:

TypeScript
this.quantity.update(value => value + 1);

the parent value is also updated.

When Two-Way Component Binding Makes Sense

Two-way binding is particularly useful for components representing editable values.

Examples include:

  • Quantity selectors
  • Toggle controls
  • Custom checkboxes
  • Rating components
  • Date selectors
  • Pagination controls
  • Numeric steppers
  • Reusable input controls

For ordinary notification events, an output is often clearer.

For example, this:

TypeScript
deleted = output<number>();

better represents a delete event than attempting to model deletion as two-way state.

Component References

Sometimes a parent needs direct access to a component or template element.

Angular template reference variables provide one way to create a reference.

Example:

HTML
<app-video-player #player></app-video-player>

<button (click)="player.play()">
  Play
</button>

Here:

Text
#player

creates a template reference to the component instance.

The template can access public properties or methods exposed by that component.

DOM Element References

Template references can also point to native elements.

Example:

HTML
<input #searchInput>

<button (click)="search(searchInput.value)">
  Search
</button>

The reference gives the template access to the input element.

When Component References Are Useful

Component references can be appropriate for imperative UI actions such as:

  • Focusing an input
  • Opening a panel
  • Starting a video
  • Resetting a reusable widget
  • Accessing a child component API

However, direct references should not replace normal data flow everywhere.

If a parent only needs to provide data, an input communicates the intention more clearly.

If a child only needs to report an event, an output is usually clearer.

Shared Services

Inputs and outputs work especially well for components with a direct relationship.

Real applications also contain components located in different parts of the component tree.

For example:

Text
HeaderComponent
       |
       |
    App Tree
       |
       |
ProductComponent

Passing the same value through multiple intermediate components simply to reach another component can make the architecture unnecessarily complicated.

A shared service may be more appropriate.

Angular services can be provided application-wide and injected into components using Angular dependency injection.

Shared Service Example

Consider a shopping-cart service.

TypeScript
import { Injectable, signal } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class CartService {
  private readonly cartCount = signal(0);

  readonly count = this.cartCount.asReadonly();

  addItem(): void {
    this.cartCount.update(value => value + 1);
  }

  removeItem(): void {
    this.cartCount.update(value =>
      Math.max(0, value - 1)
    );
  }
}

Product component:

TypeScript
import { Component, inject } from '@angular/core';
import { CartService } from './cart.service';

@Component({
  selector: 'app-product',
  template: `
    <button (click)="addToCart()">
      Add to Cart
    </button>
  `
})
export class ProductComponent {
  private readonly cartService = inject(CartService);

  addToCart(): void {
    this.cartService.addItem();
  }
}

Header component:

TypeScript
import { Component, inject } from '@angular/core';
import { CartService } from './cart.service';

@Component({
  selector: 'app-header',
  template: `
    <span>Cart: {{ cartService.count() }}</span>
  `
})
export class HeaderComponent {
  readonly cartService = inject(CartService);
}

Both components communicate indirectly through the shared state.

Angular's current guidance also demonstrates services containing signals, computed state, dependency injection, and readonly signal exposure.

Signal-Based Communication

Signals provide reactive values whose consumers can react when those values change. Angular tracks where signal state is consumed so that relevant state changes can update the application appropriately.

Signals can participate in component communication in several ways.

Signal input

TypeScript
name = input('');

Useful for:

Text
Parent → Child

Model signal

TypeScript
quantity = model(1);

Useful for:

Text
Parent ↔ Child

Signal inside a shared service

TypeScript
private readonly user = signal<User | null>(null);

Useful for:

Text
Component
    ↓
Shared Service
    ↑
Other Components

Derived Shared State

A service can expose derived values using computed().

Example:

TypeScript
import {
  Injectable,
  computed,
  signal
} from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class CartService {
  private readonly items = signal<CartItem[]>([]);

  readonly itemCount = computed(() =>
    this.items().length
  );

  readonly total = computed(() =>
    this.items().reduce(
      (sum, item) => sum + item.price * item.quantity,
      0
    )
  );
}

Components consuming itemCount or total do not need to calculate those values independently.

The service becomes the central owner of that shared state.

Complete Communication Example

Consider an application containing:

Text
ProductPageComponent
        |
        ↓
ProductCardComponent
        |
        ↓
CartService
        |
        ↓
HeaderComponent

The product page provides product information:

HTML
<app-product-card
  [product]="selectedProduct"
  (viewDetails)="openDetails($event)">
</app-product-card>

The child receives the product:

TypeScript
product = input.required<Product>();

The child reports an action:

TypeScript
viewDetails = output<number>();

A separate cart service manages global cart state:

TypeScript
readonly cartCount = signal(0);

The header reads the shared state:

HTML
Cart ({{ cartService.cartCount() }})

Different mechanisms solve different communication problems.

Trying to use one mechanism for every situation usually creates unnecessary complexity.

Choosing the Correct Communication Method

Use this practical decision guide.

RequirementSuitable Approach
Parent provides data to childinput() / input property
Child reports an action to parentoutput() / output event
Parent and child edit the same component valuemodel()
Template needs direct component accessTemplate reference
Distant components share application stateShared service
Shared state should react automaticallySignals in a service
Child requires a valueinput.required()
Child requires two-way model statemodel.required()

The relationship between components should determine the communication method.

Input vs Output vs Model

These APIs solve different problems.

FeatureInputOutputModel
Main directionParent → ChildChild → ParentParent ↔ Child
Receives dataYesNoYes
Emits changesNoYesYes
Signal-based modern APIinput()output() APImodel()
Typical useConfiguration/dataUser action/eventEditable shared value
ExampleProduct dataProduct selectedQuantity

A useful mental model is:

Text
input()  = Give the child something

output() = Child tells parent something happened

model()  = Parent and child coordinate one editable value

Inputs Are Not Global State

A common architectural mistake is passing data through many unrelated component levels.

For example:

Text
App
 ↓
Layout
 ↓
Page
 ↓
Container
 ↓
Toolbar
 ↓
UserMenu

If every component accepts:

TypeScript
user = input<User>();

only so that UserMenu eventually receives it, the intermediate components become unnecessarily coupled to that data.

For cross-cutting state such as:

  • Logged-in user
  • Shopping cart
  • Theme
  • Application preferences
  • Notifications
  • Shared filters

a service or another appropriate state-management approach may provide a cleaner architecture.

Avoid Mutating Parent-Owned Objects Carelessly

Suppose a parent passes:

TypeScript
product = {
  name: 'Laptop',
  price: 50000
};

to a child.

The child receives:

TypeScript
product = input.required<Product>();

Because objects are reference values in JavaScript, careless mutation can make data flow difficult to understand.

Instead of treating every received object as freely mutable, establish clear ownership.

A useful design principle is:

Text
Parent owns state
       ↓
Child receives state
       ↓
Child reports requested changes
       ↓
Owner updates state

This makes state changes easier to trace.

Avoid Excessive Event Chains

Consider:

Text
Component D
   ↑
Component C
   ↑
Component B
   ↑
Component A

If an event must travel through several components:

Text
D emits
C catches and emits
B catches and emits
A finally handles

the architecture may become difficult to maintain.

This can indicate that the state or action belongs in a shared service or a higher-level state-management mechanism.

Inputs and outputs remain excellent tools, but they should reflect meaningful component relationships rather than become a manual event-routing system.

Keep Component APIs Small

A reusable component with twenty inputs and fifteen outputs can become difficult to understand.

For example:

TypeScript
title = input('');
subtitle = input('');
color = input('');
size = input('');
width = input('');
height = input('');
border = input('');
font = input('');

padding = input('');
margin = input('');

Sometimes this indicates that the component abstraction is too broad.

A good component API should expose what consumers genuinely need while keeping internal implementation details private.

Ask:

Text
Does the parent really need to control this?

If the answer is no, keep it inside the component.

Prefer Typed Communication

Component communication should carry clearly defined data.

Instead of:

TypeScript
selected = output<any>();

prefer:

TypeScript
selected = output<Product>();

or:

TypeScript
selected = output<number>();

depending on what the parent actually needs.

Strong typing improves:

  • IDE autocomplete
  • Refactoring
  • Compile-time error detection
  • API clarity
  • Maintainability

The type itself also documents what information the event provides.

Emit Only the Information the Parent Needs

Suppose the parent only needs a product ID.

Instead of:

TypeScript
deleted = output<any>();

or sending an unnecessarily large object, use:

TypeScript
deleted = output<number>();

Then:

TypeScript
this.deleted.emit(product.id);

Communication contracts become easier to understand when the payload has a clear purpose.

Keep Business Logic in the Correct Place

Consider a reusable product card.

Its responsibility might be:

Text
Display product
Receive interaction
Emit selection

It should not automatically become responsible for:

Text
Updating the database
Managing global cart state
Showing application notifications
Refreshing unrelated components
Handling authentication

Those responsibilities may belong to services, containers, or other architectural layers.

Component communication works best when responsibilities are clearly separated.

Common Component Communication Mistakes

Using an output for parent-to-child data

Incorrect concept:

Text
Parent needs to provide product
→ use output

Correct:

Text
Parent → Child
→ input

Using an input for child events

An input receives data.

It does not represent a child-generated event.

Use:

TypeScript
saved = output<void>();

for an event.

Forgetting $event

If the child emits:

TypeScript
selected.emit(product);

the parent receives that value through:

HTML
(productSelected)="handleProduct($event)"

Without $event, the emitted payload is not being passed to the handler.

Calling a signal input like a normal property

With:

TypeScript
name = input('');

read:

TypeScript
this.name()

and in a template:

HTML
{{ name() }}

because the signal value is obtained by calling it.

Trying to modify an input() directly

An ordinary signal input represents a value supplied by the parent.

When the child itself must update the bound value and communicate that update, consider whether a model input is the appropriate abstraction.

Using model() for every input

Two-way communication is not automatically better than one-way communication.

Use:

TypeScript
input()

when the child only needs to consume the value.

Use:

TypeScript
model()

when the component genuinely represents an editable value that should propagate changes back to its consumer.

Using a shared service for every interaction

A service is powerful, but a simple child button click does not necessarily require global shared state.

For a direct relationship:

Text
Parent
  ↓
Child

inputs and outputs are often easier to understand.

Passing State Through Too Many Components

Repeatedly forwarding data through components that do not use it is sometimes called prop drilling.

If several unrelated components require the same state, reconsider where that state should live.

A shared service may be a better owner.

Practical Architecture Example

Imagine a dashboard containing:

Text
DashboardComponent
│
├── FilterComponent
├── ProductListComponent
│   └── ProductCardComponent
└── CartSummaryComponent

A sensible communication design could be:

Dashboard → Filter

TypeScript
initialCategory = input<string>();

Filter → Dashboard

TypeScript
filterChanged = output<Filter>();

ProductList → ProductCard

TypeScript
product = input.required<Product>();

ProductCard → ProductList

TypeScript
selected = output<Product>();

Shared cart information

Text
ProductCard
     ↓
 CartService
     ↑
CartSummary

Editable quantity component

TypeScript
quantity = model(1);

used as:

HTML
<app-quantity
  [(quantity)]="item.quantity">
</app-quantity>

This design uses each Angular feature where it naturally fits instead of forcing all communication through one mechanism.

Component Communication Design Rules

When designing communication between Angular components, follow these principles:

  1. Keep data ownership clear.
  2. Prefer one-way data flow when two-way communication is unnecessary.
  3. Use inputs for values supplied by a parent.
  4. Use outputs for events produced by a child.
  5. Use model() for genuine editable component values.
  6. Use services when state belongs to multiple components.
  7. Keep shared service state controlled rather than exposing unnecessary mutable implementation details.
  8. Use strong TypeScript types for input and output contracts.
  9. Avoid passing unrelated state through several intermediate components.
  10. Keep component public APIs smaller than their internal implementation.
  11. Name outputs according to meaningful events.
  12. Keep business logic separate from purely presentational components where practical.

Communication Pattern Summary

Text
PARENT TO CHILD

Parent State
     ↓
   input()
     ↓
Child Component
Text
CHILD TO PARENT

Child Action
     ↓
  output()
     ↓
Parent Handler
Text
TWO-WAY COMPONENT VALUE

Parent State
     ↓
   model()
     ↑
Child Update
Text
SHARED COMMUNICATION

Component A
     ↓
Shared Service
     ↑
Component B
Text
SIGNAL-BASED SHARED STATE

Component A
     ↓
Service Signal
     ↓
Computed State
     ↓
Component B

Practical Learning Checklist

After studying Angular component communication, you should be able to:

  • Explain why components need communication.
  • Identify the owner of a piece of state.
  • Pass primitive values from parent to child.
  • Pass objects and arrays to child components.
  • Use input properties correctly.
  • Create signal-based inputs with input().
  • Declare required inputs with input.required().
  • Send events from child to parent.
  • Handle emitted values using $event.
  • Create modern outputs with output().
  • Understand the difference between data and events.
  • Create model inputs with model().
  • Implement two-way component binding with [()].
  • Understand the automatically generated model change output.
  • Use template component references when direct access is appropriate.
  • Create shared services for cross-component communication.
  • Store reactive shared state using signals.
  • Expose derived state with computed().
  • Avoid unnecessary prop drilling.
  • Avoid excessive event forwarding.
  • Design small and strongly typed component APIs.
  • Choose the correct communication pattern for real Angular applications.

The most important concept is not memorizing individual APIs. It is understanding state ownership and communication direction. Once the direction is clear, the appropriate Angular mechanism usually becomes straightforward:

Text
Parent → Child        = input()
Child → Parent        = output()
Parent ↔ Child        = model()
Distant Components   = shared service
Reactive Shared State = service + signals

Question Hint