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:
- The method is being called, so no provider error. Communication with service is OK.
My tests:
- I have tried calling a random method in
IBOsNavigationElement
(the listener), and communication with service is good. - The only place where
NavigationElementsService
is added toproviders
is inNavigationModule
, 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 WithSubject
, any component that subscribes after an event has fired will NOT receive a value. To replay the previous value to late subscribers useBehaviorSubject
orReplaySubject
instead ofSubject
.More on different Subject types from http://reactivex.io/documentation/subject.html
A
BehaviorSubject
does require a default value when setting it up. So you'd need to create it with some sort of value: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.