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:
- Lift state to the common parent
- Pass data down through inputs
- 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?
2. What does @ViewChild do?
3. What is content projection in Angular?
Flashcards
Question
How do sibling components communicate?
Click to reveal answer
Answer
Shared service with an Observable or signal. The service acts as a message bus between components.
Question
What does @ViewChild do?
Click to reveal answer
Answer
Gives the parent a reference to a child component instance, allowing direct method calls.
Question
What is ng-content?
Click to reveal answer
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>