I have done this multiple times in my App. It's simple, it should work... But this time it doesn't.
My issue:
I am calling a method in a service from a Component A, my Component B is subscribed but doesn't react nor receive anything. subscribe()
is not triggering!
navigation-elements.service.ts
@Injectable() export class NavigationElementsService { updateIBOsNavigation$: Observable<any>; private updateIBOsNavigationSubject = new Subject<any>(); constructor() { this.updateIBOsNavigation$ = this.updateIBOsNavigationSubject.asObservable(); } updateIBOsNavigation(navigationData) { log.d('updateIBOsNavigation', JSON.stringify(navigationData)); this.updateIBOsNavigationSubject.next(navigationData); } }
IboDetailsGeneral
component
export class IboDetailsGeneral implements OnInit, OnDestroy { id: string; private sub: any; constructor(private route: ActivatedRoute, private iboService: IBOsService, private navigationService: NavigationElementsService) { this.sub = this.route.params.subscribe(params => { this.id = params['id']; console.log('CALLING updateIBOsNavigation FUNCTION'); this.navigationService.updateIBOsNavigation(this.id); }); } ngOnInit() { console.log('CALLING updateIBOsNavigation FUNCTION AGAIN'); this.navigationService.updateIBOsNavigation('test'); } ngOnDestroy() { this.sub.unsubscribe(); } }
This component triggers the service's method: updateIBOsNavigation
.
IBOsNavigationElement
component
export class IBOsNavigationElement implements OnInit { private id: string; constructor(private navigationService: NavigationElementsService) { this.navigationService.updateIBOsNavigation$.subscribe((navigationData) => { log.d('I received this Navigation Data:', JSON.stringify(navigationData)); this.id = navigationData; } ); } ngOnInit() { } }
This component is subscribed, it should listed and receive the data...
Let's sort out DI: Take into account that IboDetailsGeneral
is in a lower layer on the App's structure, so IboDetailsGeneral
is child of IBOsNavigationElement
.
This is why I add NavigationElementsService
into IBOsNavigationElement
's module:
NavigationModule
is IBOsNavigationElement
's module
@NgModule({ imports: [ // A lot of stuff ], declarations: [ // A lot of stuff IBOsNavigationElement ], exports: [ // A lot of stuff ], providers: [ NavigationElementsService ] })
Console:
CALLING updateIBOsNavigation FUNCTION
updateIBOsNavigation "95"
CALLING updateIBOsNavigation FUNCTION AGAIN
updateIBOsNavigation "test"
This console results tells me that:
My tests:
IBOsNavigationElement
(the listener), and communication with service is good.NavigationElementsService
is added to providers
is in NavigationModule
, so there is only one instance of the service right? Then, the issue explained in the following link doesn't take place: Angular 2 observable subscription not triggering I am really sorry for my 'wall of text' but at this point I am kind of desperate.
Any help is appretiated, thank you!
Update 1:
After the first answer I have tried several things...
Using ReplaySubject
:
@Injectable() export class NavigationElementsService { public updateIBOsNavigation$ = new ReplaySubject(); updateIBOsNavigation(navigationData) { log.d('updateIBOsNavigation', JSON.stringify(navigationData)); this.updateIBOsNavigation$.next(navigationData); } }
Result: When calling updateIBOsNavigation()
, subscribe()
is still not triggering.
Using BehaviorSubject
:
@Injectable() export class NavigationElementsService { updateIBOsNavigationSubject = new BehaviorSubject<any>(''); updateIBOsNavigation$ = this.updateIBOsNavigationSubject.asObservable(); updateIBOsNavigation(navigationData) { log.d('updateIBOsNavigation', JSON.stringify(navigationData)); this.updateIBOsNavigationSubject.next(navigationData); } }
Result: It enters subscribe()
on initialization but when I call updateIBOsNavigation()
, subscribe()
is still not triggering.
Using BehaviorSubject
v2:
I tried this approach: behaviourSubject in angular2 , how it works and how to use it
@Injectable() export class NavigationElementsService { public updateIBOsNavigation$: Subject<string> = new BehaviorSubject<string>(null); updateIBOsNavigation(navigationData) { log.d('updateIBOsNavigation', JSON.stringify(navigationData)); this.updateIBOsNavigation$.next(navigationData); } }
Result: Same as previous application of BehaviorSubject
.
Update 2:
More samples of desperate attempts after researching throughout the web...
Using BehaviorSubject
v3:
@Injectable() export class NavigationElementsService { updateIBOsNavigation$: Observable<any>; updateIBOsNavigationSubject = <BehaviorSubject<any>> new BehaviorSubject([]); constructor() { this.updateIBOsNavigation$ = this.updateIBOsNavigationSubject.asObservable(); } updateIBOsNavigation(navigationData) { log.d('updateIBOsNavigation()', JSON.stringify(navigationData)); this.updateIBOsNavigationSubject.next(navigationData); } }
Result: Same as previous BehaviorSubject
attempts... Desperation is rising...
Update 3:
Just in case, I wanted to make sure that NavigationElementsService
is a singleton:
export class NavigationModule { static forRoot() { return { ngModule: NavigationModule, providers: [NavigationElementsService] }; } }
And when importing:
imports: [ NavigationModule.forRoot() ]
Result: Same issue as always, subscribe()
not triggering, but at least I know that there is one instance of NavigationElementsService
.
I think the issue is your Subject
type With Subject
, any component that subscribes after an event has fired will NOT receive a value. To replay the previous value to late subscribers use BehaviorSubject
or ReplaySubject
instead of Subject
.
More on different Subject types from http://reactivex.io/documentation/subject.html
Behavior Subject
When an observer subscribes to a BehaviorSubject, it begins by emitting the item most recently emitted by the source Observable (or a seed/default value if none has yet been emitted) and then continues to emit any other items emitted later by the source Observable(s).
ReplaySubject
ReplaySubject emits to any observer all of the items that were emitted by the source Observable(s), regardless of when the observer subscribes.
A BehaviorSubject
does require a default value when setting it up. So you'd need to create it with some sort of value:
updateIBOsNavigationSubject = new BehaviorSubject<any>(''); updateIBOsNavigation$ = this.updateIBOsNavigationSubject.asObservable(); updateIBOsNavigation(navigationData) { log.d('updateIBOsNavigation', JSON.stringify(navigationData)); this.updateIBOsNavigationSubject.next(navigationData); }
A ReplaySubject
does not require a default value.
EDIT
When using a shared service it is also important to make sure the service is being provided ONLY at the root module. If it is provided multiple places, then the components may not be getting the same instance. Even if the service is provided at the root level, if a module between the root level and your component provides the service, a new instance will get sent down that branch of the Dependency Injection tree.
Hope this helps.
When we include a service in 'providers', then it is instantiated and this state is maintained between its component as well as its child components.
And, if we include the service in both components provider array, then it is no more following singleton. The state will be independent and not shared between them.
So, include your service only at parent component or your root component.
I too faced this issue and solved with help of this solution here, https://stackoverflow.com/a/38034298/5730167.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With