In Angular, communication between components plays a key role in building dynamic and interactive applications. Proper communication between the components ensures an efficient and smooth exchange of data and events. There are several ways to establish communication between components in Angular, each suited to specific scenarios.
Let's explore the different component communication techniques.
1) Custom property binding and event binding
Sharing Data from Parent to Child Component
The @Input decorator allows you to pass data from a parent component to a child component. By binding a property with the @Input decorator in the child component, you can receive data from the parent component.
Let's look into a simple code example.
Parent component
import { Component } from '@angular/core'; @Component({ selector: 'app-parent', template: ` <app-child [data]="parentMessage"></app-child> ` }) export class ParentComponent { parentMessage: string = "Hello! Message from parent."; }
Child component
import { Component, Input } from '@angular/core'; @Component({ selector: 'app-child', template: ` <p>{{ message }}</p> ` }) export class ChildComponent { @Input() message: string; }
Sharing data from child to parent component
The @Output decorator is used to emit data out of the child component to a parent component that listens.
Parent Component
import { Component } from '@angular/core'; @Component({ selector: 'app-parent', template: ` <app-child (message)="onMessageReceived($event)"></app-child> <p>{{ data }}</p> ` }) export class ParentComponent { data: string; onMessageReceived(data: string) { this.data= data; } }
Child component
import { Component, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app-child', template: ` <button (click)="onClick()">Send Message</button> ` }) export class ChildComponent { @Output() message = new EventEmitter<string>(); onClick() { this.message.emit("Hello! Message from child."); } }
2)Shared Services
Shared Service Communication
Shared services provide a centralized and efficient way to share data and states between different components. They facilitate communication between unrelated components and encourage code reuse and separation of concerns.
Let's create methods that set and get the shared data in the shared service.
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class SharedService { sharedData: string = 'data'; setSharedData(data: string) { this.sharedData = data; } getSharedData() { return this.sharedData; } }
Now let's have two components where one component updates the shared data in the shared service, and the other component reads and displays the updated data.
1st component
import { Component } from '@angular/core'; import { SharedService } from './shared.service'; @Component({ selector: 'app-first', template: ` <h2>First Component</h2> <input [(ngModel)]="data"/> <button (click)="setSharedData()">Set Shared Data</button> <app-second></app-second> ` }) export class FirstComponent { data: string; constructor(private sharedService: SharedService) {} setSharedData() { this.sharedService.setSharedData(this.data); } }
2nd component
import { Component } from '@angular/core'; import { SharedService } from './shared.service'; @Component({ selector: 'app-second', template: ` <h2>Second Component</h2> <p>{{ sharedData }}</p> ` }) export class SecondComponent implements OnInit{ sharedData: string; constructor(public sharedService: SharedService) {} ngOnInit() { this.sharedData= this.sharedService.getSharedData(); } }
Service Communication with Event Emitter
Using an event emitter service we can broadcast events to multiple components that have subscribed to them.
Let's create a service with an event emitter that emits values to its subscribers whenever a new value is emitted. Also, components that emit new value and subscribe to it.
Service with an event emitter
import { Injectable } from '@angular/core'; import { Subject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class SampleService { private messageEmitter = new EventEmitter<string>() emitMessage(data: string) { this.messageEmitter.next(data); } }
1st component
import { Component } from '@angular/core'; import { SampleService } from './sample.service'; @Component({ selector: 'app-first', template: ` <p>{{ data }}</p> ` }) export class FirstComponent { data: string; constructor(private sampleService: SampleService) { this.sampleService.messageEmitter.subscribe(data => { this.data = data; }); } }
2nd component
import { Component } from '@angular/core'; import { SampleService } from './sample.service'; @Component({ selector: 'app-second', template: ` <button (click)="emitData()">Emit Event</button> ` }) export class SecondComponent{ constructor(private sampleService: SampleService) {} emitData() { this.sampleService.emitMessage("Message from Second Component"); } }
3)Subjects and Observables
Subjects and BehaviorSubject can be used for sharing states across multiple components.
A subject is observable that can both emit values and subscribe to other observables. This makes it a versatile tool for exchanging data between components. When a new value is submitted to a Subject, all of its subscribers are notified. If there are no subscribers to a Subject, emitted values will be lost. It is commonly used for event handling or one-time notifications where the previous values are not necessary for subscribers.
A BehaviorSubject is a subclass of Subject that requires an initial value when created and always emits the latest value to its subscribers, even if they subscribe after emitting the value. This makes BehaviorSubject a good choice for sharing data between components that need to be synchronized.
Here is an example of how to use a Subject & BehaviorSubject with service to share data between components:
Service
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SharedService {
////Subject
private messageSubject= new Subject<string>();
event$ = this.messageSubject.asObservable();
emitEvent(data: string) {
this.messageSubject.next(data);
}
////
////BehaviourSubject
private messageBehaviourSubject = new BehaviorSubject<string>("Hello");
data$ = this.messageBehaviourSubject.asObservable();
updateData(data: string) {
this.messageBehaviourSubject.next(data);
}
}
First component
import { Subject } from 'rxjs';
@Component({
selector: 'app-first',
templateUrl: `
{{ data }}
`,
})
export class FirstComponent {
data: string;
constructor(private sharedService: SharedService) {
////Subject
this.sharedService.event$.subscribe(data => {
this.data = data;
});
////BehaviourSubject
this.sharedService.data$.subscribe(data => {
this.data = data;
});
}
}
Second component
@Component({
selector: 'app-second',
templateUrl: `
<input [(ngModel)]="inputData" />
<button (click)="updateData()">Set Shared Data</button>
`
})
export class SecondComponent {
inputData: string;
constructor(private sharedService: SharedService) {}
updateData() {
////Subject
this.eventEmitterService.emitEvent("Data from Component B");
////BehaviourSubject
this.sharedService.updateData(this.inputData);
}
4)ViewChild and ContentChild
ViewChild
It enables a parent component to access the properties and methods of a child component.
Code example:
Child Component
import { Component } from '@angular/core'; @Component({ selector: 'app-child', template: ` <p>{{ message }}</p> <button (click)="onClickSubmit()">Submit</button> ` }) export class ChildComponent { message = "Submit to order!"; onClickSubmit() { this.message = "Ordered"; } }
Parent component
import { Component, ViewChild } from '@angular/core'; import { ChildComponent } from './child.component'; @Component({ selector: 'app-parent', template: ` <app-child #childComponentRef></app-child> <button (click)="submitOrder()">Modify Child Component</button> ` }) export class ParentComponent { @ViewChild('childComponentRef') childComponent: ChildComponent; submitOrder() { this.childComponent.onClickSubmit(); }
ContentChild
It enables a parent component to access and manipulate content projected into its template.
Code example:
Parent component
import { Component, ContentChild } from '@angular/core'; @Component({ selector: 'app-parent', template: ` <ng-content></ng-content> <button (click)="updateContent()">Update</button> ` }) export class ParentComponent { @ContentChild('contentRef') content; updateContent() { this.content.nativeElement.textContent = "Content updated!"; } }
Main component
<app-parent> <p #contentRef>Content</p> </app-parent>