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 usingoutput()or@Output(). A parent can directly access a child usingviewChild()or@ViewChild(). For unrelated components, a shared service is commonly used, with Signals or RxJS providing reactive shared state.