← Back to Articles         
Component Communication

Component Communication

Published on 29 Sep 2026     9 min read Angular
Angular Components

How Does One Angular Component Communicate with Another?

What is Component Communication?

Component communication is the mechanism by which one Angular component passes data, sends events, or shares state with another component.

Angular applications are normally built from many components:

App
│
├── Header
├── ProductList
│   ├── Product
│   └── Product
└── Cart

These components often need to exchange information.

For example:

  • Parent sends product information to a child.
  • Child tells parent that a button was clicked.
  • Two unrelated components share cart information.
  • Parent directly accesses a child component when necessary.

Why Do We Need Component Communication?

Each component should have a specific responsibility.

For example:

ProductList
     │
     │ Product data
     ▼
Product
     │
     │ "Add to Cart" event
     ▼
ProductList

Without communication, components would be isolated and couldn't coordinate application behavior.


Main Ways Components Communicate

The most important communication mechanisms are:

Communication Common Approach
Parent → Child input() / @Input()
Child → Parent output() / @Output()
Parent ↔ Child direct access viewChild() / @ViewChild()
Unrelated components Shared Service
Shared reactive state Service + Signals / RxJS

Modern Angular also provides signal-based input() and output() APIs. You should know the decorator-based @Input() and @Output() syntax as well because it is common in existing Angular applications.


1. Parent → Child Communication

A parent component passes data to a child using an input.

Example

Suppose:

ProductList (Parent)
       │
       │ productName
       ▼
Product (Child)

Parent

import { Component } from '@angular/core';
import { Product } from './product';

@Component({
  selector: 'app-product-list',
  imports: [Product],
  template: `
    <app-product
      [productName]="selectedProduct">
    </app-product>
  `
})
export class ProductList {
  selectedProduct = 'Laptop';
}

Child

Using modern Angular:

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

@Component({
  selector: 'app-product',
  template: `
    <h2>{{ productName() }}</h2>
  `
})
export class Product {
  productName = input<string>();
}

Explanation

The parent has:

selectedProduct = 'Laptop';

It passes the value:

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

The child receives it:

productName = input<string>();

Flow:

ProductList
selectedProduct = "Laptop"
        │
        │ [productName]
        ▼
Product
productName()
        │
        ▼
      HTML

So:

Input = Parent sends data to Child.


Traditional @Input()

In existing Angular applications, you will frequently see:

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

@Component({
  selector: 'app-product',
  template: `
    <h2>{{ productName }}</h2>
  `
})
export class Product {

  @Input()
  productName = '';
}

Parent usage remains:

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

Conceptually both approaches solve the same problem:

Parent → Child

2. Child → Parent Communication

Suppose the child has an Add to Cart button.

When clicked, the child needs to tell the parent:

The user selected this product.

This is done using an output event.

Product (Child)
      │
      │ Event
      ▼
ProductList (Parent)

Code Sample

Child

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

@Component({
  selector: 'app-product',
  template: `
    <button (click)="addProduct()">
      Add to Cart
    </button>
  `
})
export class Product {

  productAdded = output<string>();

  addProduct() {
    this.productAdded.emit('Laptop');
  }
}

Parent

import { Component } from '@angular/core';
import { Product } from './product';

@Component({
  selector: 'app-product-list',
  imports: [Product],
  template: `
    <app-product
      (productAdded)="onProductAdded($event)">
    </app-product>
  `
})
export class ProductList {

  onProductAdded(product: string) {
    console.log(product);
  }
}

Explanation

The child declares an output:

productAdded = output<string>();

When the button is clicked:

this.productAdded.emit('Laptop');

The child emits:

Laptop

The parent listens:

(productAdded)="onProductAdded($event)"

$event contains the value emitted by the child.

Therefore:

Child

productAdded.emit("Laptop")
          │
          │
          ▼
Parent

(productAdded)="onProductAdded($event)"
          │
          ▼
onProductAdded("Laptop")

So:

Output = Child sends an event/data to Parent.


Traditional @Output()

Existing applications commonly use @Output() with EventEmitter.

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

@Component({
  selector: 'app-product',
  template: `
    <button (click)="addProduct()">
      Add to Cart
    </button>
  `
})
export class Product {

  @Output()
  productAdded = new EventEmitter<string>();

  addProduct() {
    this.productAdded.emit('Laptop');
  }
}

Parent:

<app-product
    (productAdded)="onProductAdded($event)">
</app-product>

This is very important for interviews because many production Angular codebases still contain @Input() and @Output().


Parent → Child and Child → Parent Together

A very common Angular pattern is:

                 Parent
              ProductList
                  │  ▲
                  │  │
         Input    │  │ Output
                  │  │
                  ▼  │
                 Child
                Product

Example:

<app-product
    [productName]="selectedProduct"
    (productAdded)="onProductAdded($event)">
</app-product>

Easy way to remember:

[property] = Data IN to child

(event)    = Event OUT from child

3. Parent Accessing Child with ViewChild

Sometimes a parent needs to directly access a child component.

For example, the child has:

reset()

and the parent wants to call it.

Modern Angular provides viewChild(). You will also commonly encounter the decorator-based @ViewChild() API.


Simple @ViewChild() Example

Child

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

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

  count = 10;

  reset() {
    this.count = 0;
  }
}

Parent

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

@Component({
  selector: 'app-dashboard',
  template: `
    <app-counter></app-counter>

    <button (click)="resetCounter()">
      Reset
    </button>
  `
})
export class Dashboard {

  @ViewChild(Counter)
  counter!: Counter;

  resetCounter() {
    this.counter.reset();
  }
}

Flow

Dashboard
   │
   │ @ViewChild
   ▼
Counter instance
   │
   ▼
reset()

When to Use It

Use direct child access when the parent genuinely needs to interact with a child's instance/API.

Don't use it as the default way of passing normal application data. Inputs and outputs usually produce cleaner component boundaries.


4. Unrelated Components Using a Shared Service

This is extremely important in real applications.

Consider:

Header Component

Product Component

Cart Component

Header and Product may not have a direct parent-child relationship.

Passing information through many intermediate components becomes inconvenient.

Instead, use a shared service.

Product
   │
   ▼
CartService
   │
   ├─────────────► Cart
   │
   └─────────────► Header

Shared Service with Signals

Signals provide a clean modern approach for shared reactive state.

cart.service.ts

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

@Injectable({
  providedIn: 'root'
})
export class CartService {

  cartCount = signal(0);

  addProduct() {
    this.cartCount.update(count => count + 1);
  }
}

Product Component

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

@Component({
  selector: 'app-product',
  template: `
    <button (click)="addToCart()">
      Add to Cart
    </button>
  `
})
export class Product {

  constructor(private cartService: CartService) {}

  addToCart() {
    this.cartService.addProduct();
  }
}

Header Component

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

@Component({
  selector: 'app-header',
  template: `
    <p>Cart: {{ cartService.cartCount() }}</p>
  `
})
export class Header {

  constructor(public cartService: CartService) {}
}

How It Works

Initially:

CartService

cartCount = 0

User clicks Add to Cart:

Product
   │
   │ addProduct()
   ▼
CartService
   │
   │ cartCount = 1
   ▼
Header
   │
   ▼
Cart: 1

Both components work with the same service instance when the service is provided at the appropriate shared scope, such as root.


5. Shared Service Using RxJS

Another widely used approach is RxJS with Subject or BehaviorSubject.

Example:

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class CartService {

  private cartCount =
    new BehaviorSubject<number>(0);

  cartCount$ = this.cartCount.asObservable();

  updateCount(count: number) {
    this.cartCount.next(count);
  }
}

One component updates:

this.cartService.updateCount(5);

Another component subscribes:

this.cartService.cartCount$
  .subscribe(count => {
    console.log(count);
  });

This pattern is still very common, particularly in applications already built around RxJS streams.


Which Method Should You Use?

A practical decision table is:

Scenario Common Solution
Parent → Child input() / @Input()
Child → Parent output() / @Output()
Parent directly accesses child viewChild() / @ViewChild()
Unrelated components Shared service
Shared reactive state Service + Signals
Observable/event streams Service + RxJS

Why Not Always Use a Shared Service?

Suppose you only have:

Parent
   │
   ▼
Child

Using a global service simply to pass one value may introduce unnecessary shared state.

Prefer the simplest communication mechanism that matches the relationship.

For example:

Parent → Child
     input()


Child → Parent
     output()


Unrelated Components
        │
        ▼
 Shared Service

Advantages

1. Clear Data Flow

Inputs and outputs make parent-child communication explicit.

2. Reusability

A child component doesn't need to know the implementation details of its parent.

3. Loose Coupling

Shared services can allow components to communicate without directly referencing each other.

4. Reactive Updates

Signals and RxJS can automatically propagate state changes to interested UI.

5. Maintainability

Choosing the correct communication mechanism makes large applications easier to understand.


Disadvantages

Too Many Inputs and Outputs

A deeply nested hierarchy can result in data being passed through several intermediate components:

App
 │
 ▼
A
 │
 ▼
B
 │
 ▼
C

Passing data through A → B → C when B doesn't care about it can become cumbersome.

Excessive Shared Services

Putting all application state into global services can create tightly coupled global state.

Overusing ViewChild

Directly manipulating child components can increase coupling between parent and child.

RxJS Complexity

RxJS is powerful, but complex observable chains and subscription management can make applications harder to understand if used unnecessarily.


Key Points

Parent → Child

Modern:

name = input<string>();

Traditional:

@Input()
name = '';

Usage:

<app-child [name]="productName">
</app-child>

Child → Parent

Modern:

saved = output<string>();

Emit:

this.saved.emit('Laptop');

Traditional:

@Output()
saved = new EventEmitter<string>();

Parent:

<app-child
    (saved)="onSaved($event)">
</app-child>

Unrelated Components

Use:

Shared Service
      +
Signals / RxJS

Important Interview Diagram

               COMPONENT COMMUNICATION

                    Parent
                   /      \
                  /        \
           input()          output()
         @Input()           @Output()
                /            \
               ▼              ▲
                    Child


        Parent ── viewChild()/@ViewChild() ──► Child


Component A
     │
     ▼
Shared Service
     │
     ▼
Component B

Service state:
Signals / RxJS

Interview Questions & Answers

Q1. How do Angular components communicate?

Angular components can communicate using inputs, outputs, shared services, signals/RxJS, and view queries such as ViewChild, depending on the relationship between the components.


Q2. How does a parent pass data to a child?

Using an input.

Modern Angular:

name = input<string>();

Traditional syntax:

@Input()
name = '';

Parent:

<app-child [name]="productName"></app-child>

Q3. How does a child send data to its parent?

Using an output event.

Modern Angular:

saved = output<string>();

Then:

this.saved.emit('Laptop');

The parent listens:

<app-child
    (saved)="onSaved($event)">
</app-child>

Q4. What is $event?

$event represents the value or event object produced by an Angular event binding.

For a component output:

(saved)="onSaved($event)"

If the child executes:

saved.emit('Laptop');

then $event contains:

Laptop

Q5. How do unrelated components communicate?

Usually through a shared service. The service can expose shared state using Angular Signals or RxJS observables.


Q6. What is ViewChild?

ViewChild allows a component to obtain a reference to a child component, directive, or DOM element in its view.

It can be used when the parent needs direct access to a child's public functionality.


Q7. Input vs Output — what is the difference?

Input Output
Receives data Emits events/data
Parent → Child Child → Parent
input() / @Input() output() / @Output()
Property binding Event binding

Remember:

Input  ↓
Parent → Child

Output ↑
Child → Parent

Q8. Signals vs RxJS for component communication?

Signals are well suited to synchronous reactive UI state and shared application state. RxJS is particularly useful for asynchronous streams, complex event composition, HTTP-related observable workflows, and existing observable-based architectures.


Interview-Ready Answer

Angular components communicate based on their relationship. A parent passes data to a child using input() or @Input(), and a child communicates with its parent using output() or @Output(). A parent can directly access a child using viewChild() or @ViewChild(). For unrelated components, a shared service is commonly used, with Signals or RxJS providing reactive shared state.

← Back to Articles