Exchanging data between different components is just an essential task in any application. Angular is not an exception here. Despite how common it is, there are multiple in-build approaches. Sometimes those may not be enough, and you would have to figure out some fancy workarounds.
How do you know you are not writing bad code? How do you know you are not reinventing the wheel? It is simple. You just have to learn others people mistakes and obtain their knowledge 😁
Today we will see, how to exchange data between Angular components. What are the default approaches. When those are not enough. And how to deal with more complex scenarios.
If you are ready, let’s dive right into it 🤿
In-build approaches
Parent-child relation
We will start from the basic. The simplest case is when you have a parent component that wants to pass some data to a child.

In this case, the child component needs to define a property with Input()
decorator to be able to receive any data:
@Component({
selector: 'child-component',
template: `<p>{{ textProperty }}</p>`
})
export class ChildComponent {
@Input() public textProperty: string;
}
So then, the parent can use binding to pass the data:
@Component({
selector: 'parent-component',
template: `
<child-component [textProperty]="parentProp"></child-component>
`
})
export class ParentComponent {
public parentProp = 'Hello world';
}
Child-parent relation
Often, you would like to do the opposite, send data from a child component to a parent:

This time you need another decorator Output()
and basically a callback function:
@Component({
selector: 'child-component',
template: `<button (click)="sendDataToParent()">Click Me</button>`
})
export class ChildComponent {
@Output() public onChildHasData = new EventEmitter<string>();
public sendDataToParent(): void {
this.onChildHasData.emit('Hello world');
}
}
So this time, the parent can react to the child’s event, and handle the data the way it likes:
@Component({
selector: 'parent-component',
template: `
<child-component (onChildHasData)="showChildData($event)">
</child-component>
<p>{{ parentProp }} </p>
`
})
export class ParentComponent {
public parentProp = '';
public showChildData(dataFromChild: string): void {
this.parentProp = dataFromChild;
}
}
Sibling relation
Another common issue is when you have two child components that would like to communicate with each other:

Honestly, it may look complex, but you should just use a combination of both approaches above.
The idea is simple here: send the data from one child to a parent, and then from that parent to another child.
It may sound like a code-smell approach, but for simple scenarios, it works and there is no reason to make it more complicated.
When in-build approaches are not enough
Alright, the simple cases are out of the way, now it’s time to move on to the harder ones.
Think about the scenario we had above. However, this time you have a few levels of nested components.

What are you going to do? Will you send the data to a parent, then to another parent, and so on and so forth, until you reach the deepest component in a chain? And then what? Will you push it back through all levels up again? Yeah, that does not sound well, does it? 😨
Here are some common technics that can help.
Mediator
While standard component communication is similar to Observer pattern, there is also a space for Mediator pattern.
In this case, components don’t know about each other and use a middle-man for communication. It is just a simple service with a pair of properties for each event:
@Injectable({ providedIn: 'root' })
class EventMediator
{
// customer changed event
private customerChangedSubject$ = new BehaviorSubject<CustomerData>(null);
public customerChanged = this.customerChangedSubject$.asObservable();
public notifyOnCustomerChanged(customerData: CustomerData): void {
this.customerChangedSubject$.next(customerData);
}
// product changed event
private productChangedSubject$ = new BehaviorSubject<ProductData>(null);
public productChanged = this.productChangedSubject$.asObservable();
public notifyOnProductChanged(productData: ProductData): void {
this.productChangedSubject$.next(productData);
}
}
Every event has three required components:
subject
a place where events are storedobservable
based on that subject, so component can subscribe to receive the datanotifyOfXXXChanged
a method to fire a new event
I have used BehaviourSubject
here, so a component that subscribed later still receives the last emitted value, but the choice of the subject should depend on your use case and needs.
Also, notice xxxChangedSubject$
is private
and not exposed directly. Surely, we could just use a public
subject and avoid observable and emitter method. However, in practice, it just creates a horror of global variables, when everybody has uncontrolled access to the data, resulting in hours of debugging, trying to figure which components fire the event, and which receives them. Long story short, spending minutes to do it the right way now, can save you hours later.
The usage of the mediator is pretty straightforward. To send data, just simply use the corresponding notifyOnXXXChanged()
method:
@Component({
selector: 'component-a',
template: `<button (click)="sendData()">Click Me</button>`
})
export class AComponent {
constructor(private eventMediator: EventMediator) { }
public sendData(): void {
const dataToSend = new CustomerData('John', 'Doe');
this.eventMediator.notifyOnCustomerChanged(dataToSend);
}
}
To receive the information, just subscribe to the topic you are interested in:
@Component({
selector: 'component-b',
template: `<p>{{ customerName }}</p>`
})
export class BComponent implements OnInit {
public customerName: string;
constructor(private eventMediator: EventMediator) { }
public ngOnInit(): void {
this.eventMediator
.customerChanged
.subscribe((customerData) => {
this.customerName = customerData.Name;
});
}
}
Worth mentioning, it is common to have multiple mediator services for different purposes.
Service Bus
There is another solution to the same problem which is kind of similar. Instead of using Mediator, we can rely on Service Bus.
On the one hand, components are loosely coupled, on the other, it is not so obvious who fired an event without any additional information.
// union instead of enum is encouraging
enum Events {
. . .
}
class EmitEvent {
constructor(public name: Events, public value?: any) { }
}
@Injectable({ providedIn: 'root' })
class EventBus
{
private subject = new Subject<any>();
public emit(event: EmitEvent): void {
this.subject.next(event);
}
public on(event: Events, action: any): Subscription {
return this.subject
.pipe(
filter((e: EmitEvent) => e.name === event),
map((e: EmitEvent) => e.value),
)
.subscribe(action);
}
}
Again, Service Bus is just a service. We no longer have sets of methods for each event, just emit()
and on()
are enough.
All events are stored within a single subject. With emit()
you just push a new event into it, while on()
allows you to subscribe to an event type you are interested in.
Before sending a new event, you have to declare it:
// event name
enum Events {
CustomerSelected,
. . .
}
// event data
class CustomerSelectedEventData {
constructor(public name: string) { }
}
Then a component can publish it:
@Component({
selector: 'component-a',
template: `<button (click)="sendData()">Click Me</button>`
})
export class AComponent {
constructor(private eventBus: EventBus) { }
public sendData(): void {
const dataToSend = new CustomerSelectedEventData('John');
const eventToEmit = new EmitEvent(Events.CustomerSelected, dataToSend);
this.eventBus.emit(eventToEmit);
}
}
While another one can easily consume it:
@Component({
selector: 'component-b',
template: `<p>{{ customerName }}</p>`
})
export class BComponent implements OnInit {
public customerName: string;
constructor(private eventBus: EventBus) { }
public ngOnInit(): void {
this.eventBus
.on(Events.CustomerSelected, (e: CustomerSelectedEventData) => {
this.customerName = e.name;
});
}
}
Notice, even though we have TypeScript here, it does not guarantee type safety. Change the name of the event, and TypeScript will gladly send you data of the wrong type 😔
This implementation worked fine for me for a while, but if you really want to go wild and make it type-safe as much as possible, we can 😤.
Conclusion
Angular provides you with multiple approaches for component communication. While in-build solutions abuse Observer pattern, nothing stops you from using other data exchange approaches like Mediator or Event Bus.
Even though both Mediator and Event Bus aim to solve the same problem, there are differences between those that are worth considering before taking the final solution.
Mediator exposes observables
directly to components, which brings reliability, better control over dependencies, and a pleasant debugger experience. Precise dependencies between components means not only coupling, but also a necessity for writing boilerplate code. With each event, you need a subset of similar methods. At some point, you would also need a new service, not to drown in lines of code.

On the other hand, Event Bus is more scalable and does not grow in size depending on the number of events. It is pretty agile and generic, meaning that it can be used by any component in the system. Components themselves are loosely coupled and would not change when new event appear. It may seem like an ideal approach, until one day you wake up and realize that abuse of Event Bus led you to complete misunderstanding what is going on in your system and how the heck it can be fixed 😂

In any case, it is up to you to decide what works best. Just remember, despite the chosen approach, we are dealing with observable
, meaning we have to unsubscribe.