DEV Community

Justin Miles
Justin Miles

Posted on

Communicate between Angular Components... With Encapsulation!

I saw a post here the other day by Nicolas LARRODE titled 'Communicate between Angular components'. There he described how one can communicate between two components using a service that exposes a Subject.

I won't recreate his entire post here, but you can check it out at the link above, or see (my recreation of) the resulting app here.

Essentially he creates a Subject, which is an Observable that can both be subscribed to, and made to emit (via the 'next' method.)

In my (contrived) example there are two sibling services ('brother' and 'sister'.) each displays a message from the service.

this.sharedData.data.subscribe(result => {
      this.greeting = result;
    });
Enter fullscreen mode Exit fullscreen mode
Sister Says: {{greeting}} Brother!
Enter fullscreen mode Exit fullscreen mode

That message is sent to the service by the 'app' component, and the service dutifully passes it on.

this.sharedData.data.next(greeting);
Enter fullscreen mode Exit fullscreen mode

This is a great way to share data and events between components, but there is a shortcoming. It violates encapsulation. Any component can send whatever message they want. And every sender needs to know how sending is implemented.

Right now we can make brother and sister say ANYTHING to each other. But coding is all about enforcing behavior on your objects. Otherwise why even have types?

There is a pattern called 'Observable Store' that can help us out here, and really make this data sharing a bit more robust.

The first thing we'll do is convert our Subject to a BehaviorSubject. It's another rxjs type that is similar to Subject, but has one great advantage: if a subscriber is late to the party, and subscribes after the observable has already emitted, they get the last emitted value. If our object remained a regular old Subject, the new Subscriber would just have to wait for the next value.

private _data: BehaviorSubject<string> = new BehaviorSubject('');
Enter fullscreen mode Exit fullscreen mode

You may notice we've also given our BehaviorSubject an initial value and made it private. It is now hidden from our components.

We'll expose it as a property. But not all of it, just the observable portion.

public get Data(): Observable<string>{
   return this._data.asObservable();
}
Enter fullscreen mode Exit fullscreen mode

Our subscribers can now see Data. But no one is able to write to it, they don't even know it came from a BehaviorSubject. And why should they care?!?

We'll allow them to push messages via a new method here:

SetGreeting(greeting: string){
    this._data.next(greeting);
}
Enter fullscreen mode Exit fullscreen mode

This method pushes to our private BehaviorSubject _data. The effect is that all of the subscribers to our public observable receive the new value. Cool and all, but it's just the same functionality we had before.

The cool part comes when you want to change some functionality. Say we want to prevent brother and sister from bickering. We can add this code to our new SetGreeting method:

SetGreeting(greeting: string){
    let filteredGreeting = greeting;
    if(greeting.toLowerCase() === 'i hate you'){
      filteredGreeting = 'I love you';
    }
    if(greeting.toLowerCase() === 'shut up'){
      filteredGreeting = 'Tell me more';
    }

    this._data.next(filteredGreeting);
  }
Enter fullscreen mode Exit fullscreen mode

Now if we had put this code in the app component it would have worked. But either sibling component could have overwritten it and said whatever they wanted, they could have made their SIBLING say whatever they wanted them to. That's where the real value comes in.

here's a stackblitz if you'd like to play around with it:
https://stackblitz.com/edit/angular-qjrdry

Top comments (0)