0

From what I've read, the preferred way for unrelated Angular components to communicate is to create a service and use an RxJS BehaviorSubject. Here is one of the many online references to this approach that I found:

https://fireship.io/lessons/sharing-data-between-angular-components-four-methods/

For unrelated components, this site, like others, advocates doing something similar to the following in the service:

private messageSource = new BehaviorSubject('default message');
currentMessage = this.messageSource.asObservable();

changeMessage(message: string) {
     this.messageSource.next(message)
}

Then, in the other component:

ngOnInit() {
     this.data.currentMessage.subscribe(message => this.message = message)
}

This way is pretty straightforward and I was able to share data using this approach. However, in every reference I've read about subscribing they all say it's important to unsubscribe to avoid a memory leak. There seems to be no way to unsubscribe using this approach. Is there something I'm misunderstanding or some additional step(s) required to avoid a memory leak when using this approach? Is there some approach that I should use instead?

Wilt
  • 41,477
  • 12
  • 152
  • 203
JL Gradley
  • 1,731
  • 4
  • 14
  • 15
  • I think this might answer your question... https://stackoverflow.com/questions/38008334/angular-rxjs-when-should-i-unsubscribe-from-subscription – Adam Dunkerley Mar 16 '20 at 18:00

3 Answers3

1

You can unsubscribe easily in your component ngOnDestroy method:

OtherComponent implements OnInit, OnDestroy {

  private subscription: Subscription;

  //...

  ngOnInit() {
    this.subscription = this.data.currentMessage.subscribe(
      message => this.message = message
    );
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Alternative you can do something fancy with RxJs takeWhile and a subject that you emit with a value on destroy.

You find that solution and other great examples here in this blog post on Medium.

Wilt
  • 41,477
  • 12
  • 152
  • 203
  • Thanks, but in the example subject.next(message) is used and it does not return anything (void). What needs to be changed in the service components for your suggestion? – JL Gradley Mar 16 '20 at 18:14
  • Not sure if I understand your problem. Calling `Subject.next` will make the subject emit a new value, it doesn't return a value. Instead all subscribed observers will be running and in this particular case it would mean `this.message` inside your component will be set to a new value. Hope this answers your question. Otherwise I suggest you do some reading on RxJs to get the hang of it. – Wilt Mar 16 '20 at 18:19
  • Thanks, this does work. Sorry, my disconnect was that I did not realize BehaviorSubject extends Subject. So my local variable, being specifically of Type BehaviorSubject, had no unsubscribe method. When I changed it to type Subject, as in your example, it works fine. Thanks for your help! – JL Gradley Mar 16 '20 at 18:38
0

You can unsubscribe on onDestroy lifecycle hook of your component.

subscription: Subscription

ngOnInit() {
 this.data.currentMessage.subscribe(message => this.message = message)
}

ngOnDestroy() {
 this.subscription.unsubscribe();
}

This means that this particular component will unsubscribe to the behavior subject when it is destroyed.

Akshay Rana
  • 1,455
  • 11
  • 20
0

My two cents on this:

I like the takeUntil technique mentioned in the link from Wilt (though he accidentally mentioned takeWhile). If you're happy using inheritance, I have a class which is only there to handle this issue, and then any component which might need to automatically unsubscribe from things inherits from it. It'll look something like this:

export class Unsubscriber implements OnDestroy {

    unsubscribe$: Subject<void> = new Subject()

    constructor() {}

    ngOnDestroy() {
         this.unsubscribe$.next();
         this.unsubscribe$.complete();
    }
}

Then in your component you extend that class, making sure to add the following every time you subscribe to something:

.pipe(takeUntil(this.unsubscribe$))

So in the case of your class:

export class MyClass extends Unsubscriber implements OnInit, OnDestroy {

  //...

    ngOnInit() {
        this.data.currentMessage.pipe(takeUntil(this.unsubscribe$)).subscribe(message => this.message = message);
    }
}

I like this because it will work regardless of how many subscriptions your component currently has. The only annoyance is remembering to always add that pipe, though that's no terrible burden, and even on an existing project it's not too difficult a fix.

Michael Beeson
  • 2,840
  • 2
  • 17
  • 25
  • Thanks for the input, Michael. I'm brand new to Angular. I don't understand "I like this because it will work regardless of how many subscriptions your component currently has". Is there some caveat/gotcha to using the accepted answer's method when there are multiple subscribed components? – JL Gradley Mar 19 '20 at 13:49
  • Oh what they suggest is great! It's just that if in your component you have three different subscriptions, you'll have to remember to add the unsubscribe call for each of those subscriptions in your ngOnDestroy() function. Additionally, with the takeUntil method, you don't even need that ngOnDestroy() function for unsubscribing. It's all handled by the inherited class. But the accepted answer absolutely works perfectly – just a matter of preference. – Michael Beeson Mar 19 '20 at 18:43