telion
telion

Reputation: 1120

rxjs: Subscriber does not fire on all changes in different class

I have a js class that has a BehaviorSubject and an Observable. I want to subsribe to the Observable in a different class, which does not work, properly.

//class One: 
export default ClassOne {
     constructor(){
         this._store = new BehaviorSubject({}); 
         this.store = this._store.asObservable(); 
         this.store.subscribe(data => {console.log(data)}) //logs the data, no Problem here
         //{};{ test: { foo: "bar", max: "muster" } };{ test: { foo: "bar", max: "muster" } };...
     }
     addData(data){ this._store.next(data)} //iscalled a few times.

     getStore () {return this.store}        //using a getter Function does not work either

}

//class Two
import class1 from "./class1"; 
ClassTwo {
    constructor(){
        this.one = new ClassOne(); 
        this.one.store.subscribe(data =>{console.log(data)}) //only logs {} once. Is never fired again. 
        this.one.getStore().subscribe(data =>{console.log(data)}) //Same Problem
    }
}

So My main questions: How can I make sure that the subscriber gets all the changes in ClassTwo?

Note that the Observable is defined and fires once, but is not notified with new changes.

Does it make a difference when ClassOne is a Singleton?:

  //class One: 
instance = null; 
export default ClassOne {
     constructor(){
         if (instance === null) {
             instance = this;
         }
         instance._store = new BehaviorSubject({}); 
         instance.store = this._store.asObservable(); 
         instance.store.subscribe(data => {console.log(data)}) //logs the data, no Problem here
     }
     addData(data){ instance._store.next(data)} //iscalled a few times. 
     getStore () {return instance.store}        //using a getter Function does not work either
}

Edit: Test if its a singleton (using jest)

beforeAll(() => {
   one = new ClassOne();
});
test("Singleton Test", () => {
  let one2 = new ClassOne();
  expect(one2 instanceof ClassOne).toEqual(true);
  expect(one2).toEqual(one);
  expect(one2 === one).toEqual(true);
});

Edit2: Use of Add data

beforeAll(() => {

one = new ClassOne();
two = new ClassTwo(); 
});
  test("", (done) => {
    one.addData({ test: { foo: "bar", max: "muster" } });
    one.addData({ test: { foo: "bar", max: "muster" } });
    one.addData({ test: { foo: "bar", max: "muster" } });        

    //I just coded this by heart, but the test is somthing like this
    expect(one.getStore()).toEqual(two.one.store);

    //makes sure that the subscriber have enough time
    setTimeout(() => done(), 5000);
  }, 6000);

Edit3: Using share/shareReplay

I have changed the getStore() function:

//ClassOne
getStore(){ return instance.store.pipe(share()) }
//and I tried: 
getStore(){ return instance.store.pipe(shareReplay()) }

//ClassTwo: 
this.one.getStore().subscribe(data =>{console.log(data)}) 

But the Problem remains.

Edit4: Some Trouble shooting

So after some testing and reading I come to the conclusion that subscriptions in constructors are not working properly.

While this is not working:

   let two = new ClassTwo(); //with the subscription on the store

This works:

   let two = new ClassTwo(); //without the subscription on the store
   two.subscribeToMyStore(); 

   //subscribeToMyStore: 
   subscribeToMyStore(){
       this.one.store.subscribe(data =>{console.log(data)})
   }

So why cant I do subscriptions in the constructor?

And why would it subscribe in the constructor of ClassOne but not ClassTwo?

Upvotes: 7

Views: 158

Answers (2)

telion
telion

Reputation: 1120

So the Problem was the speed of Jest. Jest finished and cleaned up everything before the logs in the subscription of classTwo were executed.

Although wierd, the workaround that worked for me was to fire the complete event in my test and only call done() in the callback of complete. Looks like this:

 beforeAll(() => {
    one = new ClassOne();
    two = new ClassTwo(); //subcription in constructor
 });
 test("", (done) => {
    one.addData({ test: { foo: "bar", max: "muster" } });
    one.addData({ test: { foo: "bar", max: "muster" } });
    one.addData({ test: { foo: "bar", max: "muster" } });        

    //I just coded this by heart, but the test is something like this
    two.one.store.subscribe({
        complete() {
            //Test the final state of everything here
            //Only call done() here. 
            done();
        },
    });
    setTimeout(
        () => one.store.complete(),   //call complete after a timeout
        1000
    );
 }, 2000);

So my takeaway here:

  • Subscribing in constructors works.
  • Make sure that the test environment dont destroy the underlying Instance of the Obeservable before they received changes.
  • Call complete on the (Behavior)Subject in a setTimeOut and call done() in the callback of complete().
  • Why the subscription in classTwo is that slow that the data is only received in classOne is still beyond me.

Upvotes: 1

Zazaeil
Zazaeil

Reputation: 4119

You have to reuse the same instance. Either ensure it is singleton and inject in through the constructor using some DI framework, or export singleton yourself from separate moudle and don't forget to share it. Note that there exist quite popular scenarios when shareReplay is needed.

Upvotes: 3

Related Questions