When I first started working with Angular, component communication was one of the most confusing aspects of the framework. I'd find myself passing data through multiple levels of components, creating complex event chains, or resorting to global state when a simpler solution existed. It took building several applications before I understood when to use each communication pattern.
The truth is, Angular gives you many ways to share data between components, and each has its place. @Input and @Output are perfect for parent-child relationships. ViewChild lets you access child components directly. Services with RxJS are ideal for sibling communication or sharing state across the app. Understanding which pattern to use when is crucial for building maintainable Angular applications.
📖 Want the complete guide with more examples and advanced patterns? Check out the full article on my blog for an in-depth tutorial with additional code examples, troubleshooting tips, and real-world use cases.
What is Angular Component Communication?
Angular Component Communication provides multiple patterns:
- @Input - Pass data from parent to child (one-way data flow)
- @Output - Emit events from child to parent
- ViewChild/ViewChildren - Access child components directly
- ContentChild/ContentChildren - Access projected content
- Services with RxJS - Share data between any components
- Route Data - Pass data through navigation
Parent to Child: Using @Input
The most common communication pattern in Angular is passing data from parent to child using @Input. This is Angular's way of implementing one-way data flow, which makes your components easier to reason about and debug.
// Child Component
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-business-settings',
templateUrl: './business-settings.component.html'
})
export class BusinessSettingsComponent {
@Input() public manageBusiness: boolean;
@Input() public businessInfo: any;
@Input() public viewDetails: boolean = false;
}
// Parent Component Template
<app-business-settings
[manageBusiness]="canManage"
[businessInfo]="currentBusiness"
[viewDetails]="isViewMode">
</app-business-settings>
@Input with Setter
React to input changes using setters:
export class BusinessSettingsComponent {
private _businessInfo: any;
@Input()
set businessInfo(value: any) {
this._businessInfo = value;
// React to changes
if (value) {
this.loadSettings(value.id);
}
}
get businessInfo(): any {
return this._businessInfo;
}
}
@Input with ngOnChanges
Detect and react to input changes:
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
export class BusinessSettingsComponent implements OnChanges {
@Input() businessInfo: any;
ngOnChanges(changes: SimpleChanges): void {
if (changes['businessInfo'] && !changes['businessInfo'].firstChange) {
// React to businessInfo changes
this.updateSettings();
}
}
}
Child to Parent: @Output and EventEmitter
Emit events from child to parent using @Output with EventEmitter:
// Child Component
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-business-settings',
templateUrl: './business-settings.component.html'
})
export class BusinessSettingsComponent {
@Output() public refresh: EventEmitter<boolean> = new EventEmitter<boolean>();
public save(): void {
// Save logic
this.refresh.emit(true);
}
public cancel(): void {
this.refresh.emit(false);
}
}
// Parent Component Template
<app-business-settings
(refresh)="handleRefresh($event)">
</app-business-settings>
// Parent Component
export class BusinessComponent {
handleRefresh(refresh: boolean): void {
if (refresh) {
this.loadBusiness();
}
}
}
Emitting Complex Data
export class BusinessSettingsComponent {
@Output() businessUpdated = new EventEmitter<Business>();
save(): void {
const updatedBusiness = {
id: this.businessInfo.id,
name: this.form.value.name,
// ... other fields
};
this.businessUpdated.emit(updatedBusiness);
}
}
// Parent
<app-business-settings
(businessUpdated)="onBusinessUpdated($event)">
</app-business-settings>
ViewChild and ViewChildren
Access child components from parent using ViewChild and ViewChildren:
import { Component, ViewChild, ViewChildren, QueryList, AfterViewInit } from '@angular/core';
import { ChildComponent } from './child.component';
@Component({
selector: 'app-parent',
template: `
<app-child #childRef></app-child>
<app-child *ngFor="let item of items"></app-child>
`
})
export class ParentComponent implements AfterViewInit {
@ViewChild('childRef') childComponent: ChildComponent;
@ViewChild(ChildComponent) firstChild: ChildComponent;
@ViewChildren(ChildComponent) children: QueryList<ChildComponent>;
ngAfterViewInit(): void {
// Access child component methods
this.childComponent.doSomething();
// Access all children
this.children.forEach(child => {
child.update();
});
}
}
ViewChild with Template Reference
export class ParentComponent {
@ViewChild('childRef', { static: false }) childComponent: ChildComponent;
callChildMethod(): void {
this.childComponent.save();
}
}
// Template
<app-child #childRef></app-child>
<button (click)="callChildMethod()">Save</button>
ContentChild and ContentChildren
Access projected content using ContentChild and ContentChildren:
// Parent Component
@Component({
selector: 'app-parent',
template: `
<app-wrapper>
<app-child #projected></app-child>
</app-wrapper>
`
})
export class ParentComponent { }
// Wrapper Component
import { Component, ContentChild, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
@Component({
selector: 'app-wrapper',
template: `<ng-content></ng-content>`
})
export class WrapperComponent implements AfterContentInit {
@ContentChild('projected') projectedChild: ChildComponent;
@ContentChildren(ChildComponent) projectedChildren: QueryList<ChildComponent>;
ngAfterContentInit(): void {
this.projectedChild.doSomething();
}
}
Service-Based Communication
Share data between any components using services with RxJS:
// Service
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataSharingService {
private dataSubject = new BehaviorSubject<any>(null);
public data$ = this.dataSubject.asObservable();
setData(data: any): void {
this.dataSubject.next(data);
}
getData(): Observable<any> {
return this.data$;
}
}
// Component 1 (Sender)
export class Component1 {
constructor(private dataService: DataSharingService) {}
sendData(): void {
this.dataService.setData({ message: 'Hello' });
}
}
// Component 2 (Receiver)
export class Component2 implements OnInit, OnDestroy {
data: any;
private subscription: Subscription;
constructor(private dataService: DataSharingService) {}
ngOnInit(): void {
this.subscription = this.dataService.getData().subscribe(data => {
this.data = data;
});
}
ngOnDestroy(): void {
this.subscription?.unsubscribe();
}
}
Using Subject for Event Broadcasting
@Injectable({
providedIn: 'root'
})
export class EventBusService {
private eventSubject = new Subject<any>();
public events$ = this.eventSubject.asObservable();
emit(event: any): void {
this.eventSubject.next(event);
}
}
// Component 1
export class Component1 {
constructor(private eventBus: EventBusService) {}
triggerEvent(): void {
this.eventBus.emit({ type: 'USER_ACTION', data: 'some data' });
}
}
// Component 2
export class Component2 implements OnInit, OnDestroy {
private subscription: Subscription;
constructor(private eventBus: EventBusService) {}
ngOnInit(): void {
this.subscription = this.eventBus.events$.subscribe(event => {
if (event.type === 'USER_ACTION') {
this.handleEvent(event.data);
}
});
}
ngOnDestroy(): void {
this.subscription?.unsubscribe();
}
}
Two-Way Data Binding
Implement two-way data binding with @Input and @Output:
// Child Component
export class ChildComponent {
@Input() value: string;
@Output() valueChange = new EventEmitter<string>();
updateValue(newValue: string): void {
this.value = newValue;
this.valueChange.emit(newValue);
}
}
// Parent Component Template
<app-child
[(value)]="parentValue">
</app-child>
// Equivalent to:
<app-child
[value]="parentValue"
(valueChange)="parentValue = $event">
</app-child>
Choosing the Right Pattern
Here's my decision framework for choosing communication patterns:
Parent to Child?
Use @Input. It's simple, performant, and follows Angular's one-way data flow principle. This covers about 70% of component communication needs.
Child to Parent?
Use @Output with EventEmitter. It's explicit, type-safe, and easy to test. Perfect for user actions that need to bubble up.
Need to Access Child Methods?
Use ViewChild or ViewChildren. This is useful when you need to call methods on child components or access their properties directly.
Sibling Components or App-Wide State?
Use a service with RxJS. BehaviorSubject is perfect for shared state, while Subject works well for event broadcasting.
Need to Access Projected Content?
Use ContentChild or ContentChildren. For accessing content projected via <ng-content>.
Advanced Patterns
Combining Multiple Patterns
export class ParentComponent {
@ViewChild(ChildComponent) child: ChildComponent;
constructor(private dataService: DataSharingService) {}
// Use ViewChild to call child method
triggerChildSave(): void {
this.child.save();
}
// Use service for sibling communication
shareDataWithSiblings(): void {
this.dataService.setData({ shared: 'data' });
}
}
Using Route Data
// Navigate with data
this.router.navigate(['/details'], {
state: { business: this.business }
});
// Receive in component
export class DetailsComponent implements OnInit {
business: Business;
constructor(private router: Router) {}
ngOnInit(): void {
this.business = history.state.business;
}
}
Best Practices
- Use @Input for one-way data flow - From parent to child
- Use @Output with EventEmitter - For child to parent communication
- Use services with RxJS - For sibling component communication
- Use ViewChild/ViewChildren - To access child component APIs
- Avoid direct component references - When possible, prefer @Input/@Output
- Use BehaviorSubject for shared state - Maintains current value
- Unsubscribe from observables - Prevent memory leaks
- Keep communication simple and explicit - Avoid over-engineering
- Use OnPush change detection - When appropriate for performance
- Document component inputs and outputs - Clear API documentation
Memory Leak Prevention
export class Component implements OnInit, OnDestroy {
private subscriptions = new Subscription();
ngOnInit(): void {
// Add all subscriptions
this.subscriptions.add(
this.dataService.getData().subscribe(data => {
// Handle data
})
);
}
ngOnDestroy(): void {
// Unsubscribe from all
this.subscriptions.unsubscribe();
}
}
Common Patterns
Parent-Child Communication Flow
Parent Component
↓ [@Input]
Child Component
↓ [@Output]
Parent Component
Sibling Communication Flow
Component A
↓ [Service]
Shared Service (RxJS)
↓ [Observable]
Component B
Component Hierarchy Communication
Grandparent
↓ [@Input]
Parent
↓ [@Input]
Child
↑ [@Output]
Parent
↑ [@Output]
Grandparent
Resources and Further Reading
- 📚 Full Angular Component Communication Guide - Complete tutorial with advanced examples, troubleshooting, and best practices
- Angular Reactive Forms Guide - Form handling with component communication
- Angular Services Guide - Dependency injection for services
- Angular Guards Guide - Route protection patterns
- Angular Component Communication Docs - Official Angular documentation
- RxJS Documentation - Reactive programming library
- Angular ViewChild API - ViewChild reference
Conclusion
Angular Component Communication provides multiple patterns for sharing data between components. Understanding when to use each pattern is crucial for building maintainable Angular applications.
Key Takeaways:
- @Input/@Output - For parent-child communication
- ViewChild/ViewChildren - For accessing child components
- ContentChild/ContentChildren - For accessing projected content
- Services with RxJS - For sibling and app-wide communication
- Choose the simplest pattern - Start with @Input/@Output
- Prevent memory leaks - Always unsubscribe from observables
- Keep it simple - Avoid over-engineering communication
The key is to start with the simplest pattern that works (@Input/@Output), and only move to more complex solutions (services, ViewChild) when you actually need them. Over-engineering component communication is a common mistake. Keep it simple, and your future self will thank you.
What's your experience with Angular Component Communication? Share your tips and tricks in the comments below! 🚀
💡 Looking for more details? This is a condensed version of my comprehensive guide. Read the full article on my blog for additional examples, advanced patterns, troubleshooting tips, and more in-depth explanations.
If you found this guide helpful, consider checking out my other articles on Angular development and frontend development best practices.
Top comments (0)