0

I want to share a state (just a string) with multiple components. To do so I came across this site and implemented it to my app like this:

my.component.ts

// ...
onButtonClick(event: MouseEvent) {
  console.log('1');
  this.myService.getStatus().subscribe(
    (currentStatus) => {
      console.log('2');
      if (currentStatus=== 'success') {
        // foo
      } else {
        // bar
      }
    },
    (error) => { /* ... */ },
    () => { /* ... */ }
  );
  console.log('3');
}
// ...

Note: I added the console.logs to debug.

This is my service

import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class MyService {
  private status = new Subject<string>();

  constructor() { }

  getStatus(): Observable<string> {
    return this.status .asObservable();
  }

  updateStatus(updatedStatus: string) {
    this.status.next(updatedStatus);
  }
}

My problem is now, that I don't understand, why subscribe() gets fired after I update the status with another button, so the call stack is something like *Button Click* -> '1' -> '3' -> *btnUpdate click* -> '2'. I do call updateStatus() earlier to set a value.

I used observables elsewhere in the same project but they behave as expected, what is different to this one?


Edit

This is for future readers: Refer the answer of Yevhenii Dovhaniuk, he gave me a better undestanding of Observables and his answer worked as well

ArmandoS63
  • 693
  • 1
  • 6
  • 21
  • 1
    Try using BehaviorSubject instead of Subject. A BehaviorSubject holds one value, when it is subscribed it emits the value immediately. A Subject doesn't hold a value. – Shail_bee Nov 19 '19 at 09:37
  • Can we see the code where you are calling update status???... – pavan kumar Nov 19 '19 at 09:39

4 Answers4

2

that I don't understand, why subscribe() gets fired after I update the status with another button

Because you are using rxJs Subject which doesn't hold any value by default, so when you have updated the status using first button click it doesn't hold that value and when you clicked another button for updates it return the previous value. To give you an example:

const subject = new Subject();
subject.next(1);
subject.subscribe(x => console.log(x));

Here console output will be empty. So to fix this you might want to use BehaviorSubject which holds one value initially, so when you subscribe to it, it will return the value immediately.

Take a look here more detail about Subject and BehaviorSubject.

Shail_bee
  • 499
  • 4
  • 24
1

It comes to a definition of cold and hot Observables, visit official docs for more details. In few words, your Subject is cold Observable meaning that it will be executed each time you subscribe to it and will be destroyed if there are no subscribers active atm.

So to fix your problem either add a subscription in your constructor like

export class MyService implements OnDestroy {
  private status = new Subject<string>();
  private statusSubjectSubscription: Subscription;

  constructor() { 
    this.statusSubjectSubscription = this.status.subscribe(); // here
  }

  ngOnDestroy() {
    if (this.statusSubjectSubscription) {
      this.statusSubjectSubscription.unsubscribe() // don't forget to unsubscribe
    }
  }

  getStatus(): Observable<string> {
    return this.status.asObservable();
  }

  updateStatus(updatedStatus: string) {
    this.status.next(updatedStatus);
  }
}

or change a type of your Subject to either ReplaySubject or BehaviorSubject (please note that the behavior may change)

Yevhenii Dovhaniuk
  • 1,073
  • 5
  • 11
1

To complete previous answers, you need to unsubscribe from your Observable to avoid memory leaks

//Each time a subscribe is done, a Subscription is given in return
//Here your Subscription is lost leading to memory leak.
onButtonClick(event: MouseEvent) {
  this.myService.getStatus().subscribe(...);
}

It is also preferable to subscribe in ngOnInit and to store the Subscription in a class attribute that will be destroyed in ngOnDestroy

ngOnInit(){
    this.subscription = this.myService.getStatus().subscribe(...);
}

ngOnDestroy(){
    this.subscription.unsubscribe();
}
Axiome
  • 695
  • 5
  • 18
0

I would suggest subscribing in your onInit-function, as the subscribe now starts on your button click. It does not have time to finish before the console log below, which in turn also prints 1 -> 3 before 2.

aTON
  • 25
  • 6
  • I know that the order is 1 -> 3 -> 2, the point was more to show the sequence of the button clicks, but thank you anyway – ArmandoS63 Nov 19 '19 at 09:39