This article is part of a series on Inter-Component Communication in Angular. While you can start anywhere, it’s always best to start at the beginning right!
Part 1 – Input Binding
Part 2 – Output Binding
Part 3 – Joining Service
Part 4 – ViewChild
In our two previous posts in using Input Binding, and then Output Binding for intercomponent communication, one thing is common between the two – there needs to be a direct parent/child relationship between the two components that need to talk to each other. While this is often the case, with a parent component passing down values to a child component, it’s also pretty common that components need to talk to each other either as siblings, or even distant cousins! For that, the best thing to use, hands down, is a “joining service”.
A good example of this is our guide to building a multi step wizard in angular. There, we used a joining service to hold the values of the individual form steps, and then at the end bring them all together. While this may not seem like “inter-component communication” at first, when you think about how each step of the wizard needs to pass on it’s values to the next step, then it totally makes sense!
Value Sharing via Joining Service
The first example I want to show is how we can share values between services. It’s actually pretty simple. Let’s create a service called “MyJoiningService” :
@Injectable({
providedIn: 'root'
})
export class MyJoiningService {
constructor() { }
public myValue : string;
}
Nothing too fancy, we are just exposing a string value. Importantly, we have added the providedIn: “root” declaration. This means (In simple terms), that there will only ever be one instance of this service available, no matter which component uses it. So if we do this in our FirstComponent :
export class FirstComponent implements OnInit {
constructor(private myJoiningService : MyJoiningService) { }
ngOnInit() {
this.myJoiningService.myValue = "ABC";
}
}
And then in our second component we do :
export class SecondComponent implements OnInit {
constructor(private myJoiningService : MyJoiningService) { }
ngOnInit() {
let someValue = this.myJoiningService.myValue; //The value is ABC
}
}
The myValue property inside JoiningService is now shared between two components, even if the two components aren’t related to each other directly in HTML.
It’s really not rocket science and when spelled out like this, it probably seems a little too easy. Really the providedIn declaration is doing the magic for us, being able to share the exact same instance/singleton within our application.
Now this example works, but only if these two components aren’t on the same page. Otherwise we have a little bit of a race condition in that our SecondComponent needs to read the value *after* it has been set by the FirstComponent. But what if the value is set by a user interaction? How can we “alert” the SecondComponent the value has changed, by way of our joining service?
Using Subjects via Joining Service
To solve our problem of being “notified” when a new value is passed into our Joining Service, we are going to use Subjects. So let’s modify our service to look like so :
export class MyJoiningService {
constructor() { }
private myValue = new ReplaySubject<string>(1);
public addMyValue = (value : string): void => this.myValue.next(value);
public onMyValue = this.myValue.asObservable();
}
We have made “myValue” now be a Subject (Although in this example, we are using ReplaySubject, for why that is, check out our article on Subject vs ReplaySubject here : http://angulartut.onpressidium.com/2020/12/12/subject-vs-replaysubject-vs-behaviorsubject/).
We then have two additional properties. The first is an “addMyValue” method, that adds a value into the subject. And the second is an “onMyValue” property which acts as an observable for components to subscribe to changes.
We can then modify our FirstComponent like so to pump a value into the joining service :
export class FirstComponent implements OnInit {
constructor(private myJoiningService : MyJoiningService) { }
ngOnInit() {
this.myJoiningService.addMyValue("ABC");
}
}
And our SecondComponent can then listen for the value like so :
export class SecondComponent implements OnInit {
someValue : string;
constructor(private myJoiningService : MyJoiningService) { }
ngOnInit() {
this.myJoiningService.onMyValue.subscribe(x => {
this.someValue = x;
})
}
}
Note that the use of the ReplaySubject is nice here because it doesn’t matter when the SecondComponent starts listening – we’ve managed to remove our race condition! Even if it “subscribes” late, because we use a ReplaySubject, it will still give us the last value that was passed into the JoiningService.
I use this pattern a tonne when trying to sync up components that have no direct relationship to one another. For example, a recent application of mine was built to receive inbound calls via Twilio in the browser, however when an inbound call was received, we wanted to disable some functionality of a couple of components so that you didn’t get direct messages while on your inbound call. We did this by creating a call service that would be able to alert other components when a call was in progress, without the need for direct component to component communication.
The other obvious benefit of using this is that it decouples components from one another. In our example, FirstComponent doesn’t know about SecondComponent, and vice versa. FirstComponent just knows that it can push values into the joining service for any number of other components to listen to, without needing to know who is subscribing to the observable.
Next
In the final part of our series, we are going to look at how we can use ViewChild in very select scenarios where we have a direct component relationship, but need to do more than just pass values around. It’s a rare usecase, but I do end up using it once or twice in each project when I’m in a jam. Check it out here : Inter-Component Communication In Angular – ViewChild
💬 Leave a comment