In Angular, building interactive applications often requires communication between components. Two of the most common patterns are parent-to-child communication and child-to-parent communication. Angular provides @Input()
and @Output()
decorators to facilitate this communication efficiently. Mastering these techniques is crucial for developing dynamic, maintainable, and scalable applications.
In this guide, we will cover:
- Introduction to component communication
- Using
@Input()
for parent-to-child data flow - Using
@Output()
andEventEmitter
for child-to-parent communication - Practical examples: dynamic lists, forms, and user interactions
- Best practices for parent-child communication
- Conclusion
1. Introduction to Component Communication
Components in Angular are isolated and encapsulated, which promotes modularity. However, real-world applications require sharing data or events between parent and child components. Angular provides:
@Input()
: Allows the parent to pass data into a child component@Output()
: Allows the child to emit events or data to the parent
This pattern ensures unidirectional data flow and clear separation of responsibilities.
2. Using @Input() for Parent-to-Child Communication
The @Input()
decorator allows data binding from parent to child. The child component declares an input property, which the parent can bind to using property binding [property]
.
2.1 Basic Example
Child Component
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: <p>Hello, {{ name }}!</p>
})
export class ChildComponent {
@Input() name: string = '';
}
Parent Component Template
<app-child [name]="parentName"></app-child>
Parent Component Class
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
templateUrl: './parent.component.html'
})
export class ParentComponent {
parentName = 'Angular Developer';
}
Explanation:
parentName
in the parent is bound to the child’sname
input.- When
parentName
changes, Angular automatically updates the child component view.
2.2 Using Input for Dynamic Data
@Input() items: string[] = [];
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
Parent Template:
<app-child [items]="productList"></app-child>
Parent Class:
productList = ['Laptop', 'Phone', 'Tablet'];
This allows dynamic lists or data arrays to be passed from parent to child seamlessly.
3. Using @Output() and EventEmitter for Child-to-Parent Communication
@Output()
allows a child component to emit events to the parent. This is often combined with EventEmitter.
3.1 Basic Example
Child Component
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: <button (click)="notifyParent()">Notify Parent</button>
})
export class ChildComponent {
@Output() notify: EventEmitter<string> = new EventEmitter<string>();
notifyParent() {
this.notify.emit('Hello from Child!');
}
}
Parent Component Template
<app-child (notify)="handleNotification($event)"></app-child>
<p>{{ message }}</p>
Parent Component Class
message: string = '';
handleNotification(msg: string) {
this.message = msg;
}
Explanation:
- Child emits a message using
EventEmitter
. - Parent listens for
(notify)
and updates its state accordingly.
3.2 Using Output with Dynamic Data
For example, deleting an item from a list in the parent via child component:
Child Component
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-item',
template: `
<li>
{{ item }}
<button (click)="deleteItem()">Delete</button>
</li>
`
})
export class ItemComponent {
@Input() item: string = '';
@Output() remove: EventEmitter<string> = new EventEmitter<string>();
deleteItem() {
this.remove.emit(this.item);
}
}
Parent Component Template
<ul>
<app-item
*ngFor="let product of products"
[item]="product"
(remove)="removeProduct($event)">
</app-item>
</ul>
Parent Component Class
products = ['Laptop', 'Phone', 'Tablet'];
removeProduct(product: string) {
this.products = this.products.filter(p => p !== product);
}
Explanation:
- Child emits the item to remove.
- Parent updates the products array dynamically.
- This pattern is useful for lists, tables, or interactive UI elements.
4. Practical Examples
4.1 Parent-Child Form Communication
Child component receives form configuration from the parent and emits form submission events.
Child Component
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'app-dynamic-form',
template: `
<form [formGroup]="form" (ngSubmit)="submitForm()">
<div *ngFor="let field of fields">
<label>{{ field.label }}</label>
<input [formControlName]="field.name" />
</div>
<button type="submit">Submit</button>
</form>
`
})
export class DynamicFormComponent {
@Input() fields: { name: string; label: string }[] = [];
@Output() submit: EventEmitter<any> = new EventEmitter<any>();
form = new FormGroup({});
ngOnChanges() {
const group: any = {};
this.fields.forEach(f => {
group[f.name] = new FormControl('');
});
this.form = new FormGroup(group);
}
submitForm() {
this.submit.emit(this.form.value);
}
}
Parent Component
formFields = [
{ name: 'username', label: 'Username' },
{ name: 'email', label: 'Email' }
];
handleFormSubmit(data: any) {
console.log('Form Data:', data);
}
<app-dynamic-form [fields]="formFields" (submit)="handleFormSubmit($event)"></app-dynamic-form>
Explanation:
- Parent defines form fields.
- Child builds the form dynamically.
- Child emits form values upon submission.
4.2 Real-Time User Interaction
Child component receives initial data from the parent and emits updates based on user actions.
@Component({
selector: 'app-counter',
template: `
<button (click)="increment()">+</button>
<span>{{ count }}</span>
<button (click)="decrement()">-</button>
`
})
export class CounterComponent {
@Input() count: number = 0;
@Output() countChange: EventEmitter<number> = new EventEmitter<number>();
increment() {
this.count++;
this.countChange.emit(this.count);
}
decrement() {
this.count--;
this.countChange.emit(this.count);
}
}
Parent Template
<app-counter [(count)]="parentCount"></app-counter>
<p>Parent Count: {{ parentCount }}</p>
Explanation:
- Two-way binding using
@Input()
and@Output()
enables real-time synchronization. - Parent component updates automatically as child changes.
5. Best Practices for Parent-Child Communication
- Keep components isolated
- Parent and child should not manipulate each other’s internal state directly.
- Use
@Input()
for data flow from parent to child- Avoid direct property assignment.
- Use
@Output()
and EventEmitter for child-to-parent communication- Promotes unidirectional data flow.
- Prefer immutable data structures
- Changing references ensures Angular detects updates.
- Use meaningful event names
- Example:
(deleteItem)
instead of(click)
.
- Example:
- Limit complexity in communication
- For complex interactions, consider using services with RxJS Observables.
- Avoid excessive parent-child chaining
- For deeply nested components, services or state management may be better.
Leave a Reply