23

I'm trying to create my own observable service but after i get the initial data from the service, any updates to the service aren't propagated to any subscribers. Service looks like this:

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

@Injectable()
export class DataService {
  keys : number[] = [4,5,1,3,2];
  private data :BehaviorSubject<number[]> = new BehaviorSubject(this.keys);

  constructor() {};

  public setKey(i:number, val:number) :void {
    this.keys[i]=val;
    this.data.next(this.keys);
  }
  public getData() :Observable<number[]> {
    return new Observable(fn => this.data.subscribe(fn));
  }
  public getKeys() :number[] {
    return this.keys;
  }
}

the component using the service gets the initial data just fine. that component gets its data in the constructor:

constructor(public dataService: DataService) {
    dataService.getData().subscribe(data => {
      console.log("Gotcha!");
      this.data.datasets[0].data = data)
    });
};

which gives one Gotcha in the console log. But after updating the data with setKey(2,3) somewhere else, i was expecting the this.data.next(this.keys); to send data to all subscribers and data would be updated inside that component accordingly. but no data is sent to the subscribers..

i thought i figured the Observables out but please dont be friendly if i'm missing the point here ;) any pointers in the right direction will be greatly appreciated!

kjoetools
  • 478
  • 1
  • 4
  • 11
  • 14
    Sounds like you are providing the service more than once which leads to different components getting different instances. – Günter Zöchbauer Aug 22 '16 at 15:55
  • 1
    What should be the approach when working in modules. I have a similar service. Some of my components (in different modules) subscribe on an observable in my auth service and receive data when it changes but others don't receive data. Eg. when my login (btn) component emits next on the observer the login components sees the change but my header & sidebar components don't while they are also subscribed, I think it has to do with lazy loading or so, or cache, or some parent child problem.. pff – webmaster Sep 26 '16 at 00:23
  • 2
    @GünterZöchbauer thanks for this! I was providing the service both as part of the component and in the parent module. It was driving me crazy! – jminuscula Oct 14 '17 at 16:30
  • 1
    @GünterZöchbauer I literally lost hours on this. Thank you so much. – Tasos Anesiadis Feb 06 '19 at 02:37
  • 1
    @GünterZöchbauer. Thanks a lot. i'm new to Angular, it helped me to fix the issue quickly. – BALAJI RAJ Jun 19 '20 at 08:37

2 Answers2

41

I faced the same issue at some point. It's likely the reason is that your service is not a singleton, i.e. that every subscriber gets a new instance. Contrary to Angular 1, in A2 services are not singletons.

If you want to have one instance of the service shared by multiple services/components, put it in providers of your parent @Component or @NgModule.

@NgModule({
  declarations: [],
  imports: [],
  bootstrap: [AppComponent],
  providers: [DataService]
})
export class AppModule {
}
Konrad Garus
  • 50,165
  • 42
  • 145
  • 220
  • 1
    i had it in bootstrap but also in the providers inside the components. removing it from made it all work :) tnx! wondering though, i was on RC.4 and had no @NgModule. You're referring to RC.5 probably? when i upgraded i got all kinds of weird traceur messages in the console and it broke my application (following the angular2 site's upgrade path). have you tried their upgrade path and got away with it? – kjoetools Aug 23 '16 at 08:00
  • I only started with RC5, so I am not really familiar with the upgrade path and its quirks. Lucky me, I guess. :-) – Konrad Garus Aug 23 '16 at 09:33
  • After almost 4 hours of trying literally every single solution, this finally solved all of my problems. Thank you so much – Emil Pedersen May 15 '17 at 00:18
  • this one was my solution. 3 years later in 2019 and still not one BehaviourSubject tutorial i went through mentioned anything about the singleton problem or the proper place to set the service provider like Konrad mentions here. Thanks Konrad. – bkdraper Feb 13 '19 at 06:40
  • @Konrad Garus. Thanks a lot, your solution worked like a charm. – BALAJI RAJ Jun 19 '20 at 08:44
  • Do we still need to do this if in the service its mentioned as @Injectable({providedIn: 'root'}) ? plz help! – Kshri Jun 28 '20 at 10:22
12

Every time you emit a value, you should create a new object instead of emitting the same mutated object. That will protect you from change detection issues. It'd be better to always assign a new Object to this.keys when you change it though.

this.data.next([...this.keys]);

You can use asObservable() here

public getData(): Observable<number[]> {
  return this.data.asObservable();
}

Also check for what Günter Zöchbauer said. Are you providing the service as a singleton?

j2L4e
  • 5,956
  • 25
  • 38
  • that works like a charm! (after i corrected my rookie mistake that Gunter pointed out *cough*cough*).. – kjoetools Aug 22 '16 at 23:16
  • How to deal with a long living object that is not supposed to be changed, say a object reflecting a REST resource that mutates over time. Is there some workaround? I want to anyway update my view listening to changes emitted by a `BehaviorSubject` exactly for such a case. – Wilt Nov 20 '19 at 17:07