Back to Blog
Angular18 min read

Angular Component Communication: Complete Guide

Angular provides multiple ways for components to communicate. Learn how to use @Input and @Output, EventEmitter, ViewChild, ContentChild, services with RxJS, and other patterns for effective component communication in Angular applications.

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.

In this guide, I'll walk through each communication pattern with real examples from applications I've built. I'll explain not just how they work, but when to use them and what pitfalls to avoid. By the end, you'll have a clear mental model for choosing the right communication strategy for any scenario.

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. I use this pattern constantly—it's the foundation of most component hierarchies.

Here's a practical example from a business management application I built. The parent component manages the business data, and the child component displays and allows editing of that data:

// 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>

Child to Parent: @Output and EventEmitter

Emit events from child to parent:

// 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();
    }
  }
}

ViewChild and ViewChildren

Access child components from parent:

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();
    });
  }
}

Service-Based Communication

Share data through 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 {
  data: any;
  
  constructor(private dataService: DataSharingService) {}
  
  ngOnInit(): void {
    this.dataService.getData().subscribe(data => {
      this.data = data;
    });
  }
}

ContentChild and ContentChildren

Access projected content:

// 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 } 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();
  }
}

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
  • Use BehaviorSubject for shared state management
  • Unsubscribe from observables to prevent memory leaks
  • Keep component communication simple and explicit
  • Use OnPush change detection strategy when appropriate
  • Document component inputs and outputs clearly

Choosing the Right Pattern: My Decision Framework

After working with Angular for years, I've developed a simple decision framework for choosing communication patterns. Here's how I think about it:

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.

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 I see in Angular codebases. Keep it simple, and your future self will thank you.