Skip to content
intermediate Phase 3 · Components, Templates, and Lifecycle

Parent-Child Communication Patterns

Coordinate components through inputs, outputs, projection, and services.

40m
0 problems
Topic Progress 0%

Communication Patterns Overview

Angular provides several patterns for component communication. Choosing the right one depends on the relationship between components.

Pattern Selection Guide

Relationship Pattern
Parent to Child @Input / input()
Child to Parent @Output / output()
Parent to Child (complex) @ViewChild / viewChild()
Child to Parent (query) @ContentChild / contentChild()
Siblings Shared service with Observable
Any to Any State management (NgRx, signals)
Content projection ng-content

Inputs and Outputs (Review)

// Child
@Component({
  selector: 'app-child',
  template: `<p>{{ message }}</p><button (click)="send()">Send</button>`,
})
export class ChildComponent {
  @Input() message!: string;
  @Output() reply = new EventEmitter<string>();

  send(): void {
    this.reply.emit('Response from child');
  }
}

// Parent
@Component({
  selector: 'app-parent',
  template: `
    <app-child [message]="parentMessage" (reply)="onReply($event)"></app-child>
    <p>{{ childReply }}</p>
  `,
})
export class ParentComponent {
  parentMessage = 'Hello child';
  childReply = '';

  onReply(message: string): void {
    this.childReply = message;
  }
}

@ViewChild — Parent Accessing Child

@Component({
  selector: 'app-parent',
  template: `<app-child></app-child><button (click)="childMethod()">Call Child</button>`,
})
export class ParentComponent implements AfterViewInit {
  @ViewChild(ChildComponent) child!: ChildComponent;

  ngAfterViewInit(): void {
    // Safe to access child here
  }

  childMethod(): void {
    this.child.someMethod();
  }
}

@ContentChild — Accessing Projected Content

@Component({
  selector: 'app-wrapper',
  template: `<div class="wrapper"><ng-content></ng-content></div>`,
})
export class WrapperComponent implements AfterContentInit {
  @ContentChild('projectedHeader') header!: ElementRef;

  ngAfterContentInit(): void {
    console.log('Projected header:', this.header?.nativeElement.textContent);
  }
}
<app-wrapper>
  <h1 #projectedHeader>Hello</h1>
  <p>Content goes here</p>
</app-wrapper>

Sibling Communication via Shared Services

When components are not in a parent-child relationship, a shared service with an Observable enables communication.

Shared Service Pattern

@Injectable({ providedIn: 'root' })
export class MessageService {
  private messages = new BehaviorSubject<string[]>([]);
  messages$ = this.messages.asObservable();

  addMessage(message: string): void {
    this.messages.next([...this.messages.getValue(), message]);
  }

  clear(): void {
    this.messages.next([]);
  }
}
// Component A — sends messages
@Component({
  selector: 'app-sender',
  template: `
    <input [(ngModel)]="newMessage">
    <button (click)="send()">Send</button>
  `,
})
export class SenderComponent {
  newMessage = '';

  constructor(private messageService: MessageService) {}

  send(): void {
    this.messageService.addMessage(this.newMessage);
    this.newMessage = '';
  }
}
// Component B — receives messages
@Component({
  selector: 'app-receiver',
  template: `
    <div *ngFor="let msg of messages$ | async">
      {{ msg }}
    </div>
  `,
})
export class ReceiverComponent {
  messages$ = this.messageService.messages$;

  constructor(private messageService: MessageService) {}
}

Why Not Use @Input/@Output for Siblings?

Inputs and outputs require a direct parent-child relationship. For siblings, you would need to:

  1. Lift state to the common parent
  2. Pass data down through inputs
  3. Emit events up through outputs

This creates prop drilling and tight coupling. A shared service is cleaner.

Signal-Based Service Communication (Modern)

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

  addItem(item: CartItem): void {
    this.items.update(items => [...items, item]);
  }

  removeItem(id: number): void {
    this.items.update(items => items.filter(i => i.id !== id));
  }

  get total(): number {
    return this.items().reduce((sum, item) => sum + item.price, 0);
  }
}
@Component({
  selector: 'app-cart-icon',
  template: `<span class="badge">{{ cartService.cartItems().length }}</span>`,
})
export class CartIconComponent {
  constructor(public cartService: CartService) {}
}

Quiz

1. What pattern should you use for sibling component communication?

Question 1 options

2. What does @ViewChild do?

Question 2 options

3. What is content projection in Angular?

Question 3 options

Flashcards

Question

How do sibling components communicate?

Answer

Shared service with an Observable or signal. The service acts as a message bus between components.

Question

What does @ViewChild do?

Answer

Gives the parent a reference to a child component instance, allowing direct method calls.

Question

What is ng-content?

Answer

Content projection — inserts parent markup into designated placeholders in the child template.

Revision Notes

Key Takeaways

  • 1. @Input flows data down, @Output flows events up
  • 2. Siblings communicate via shared services
  • 3. @ViewChild gives parent access to child instance
  • 4. ng-content enables content projection

Interview Tips

  • • Explain when to use each communication pattern
  • • Be ready to describe prop drilling and why services avoid it
  • • Mention signal-based services as the modern approach

Cheat Sheet

Component Communication Cheat Sheet

Parent to Child:

@Input() data!: Type;
// or
data = input<Type>();

Child to Parent:

@Output() event = new EventEmitter<Type>();
// or
event = output<Type>();

Siblings:

@Injectable({ providedIn: 'root' })
export class SharedService {
  private data = new BehaviorSubject<Type>(initial);
  data$ = this.data.asObservable();
}

Content projection:

<app-wrapper>
  <h1 #header>Projected</h1>
</app-wrapper>

Continue Learning