Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2: Observable / Subscription not triggering

Tags:

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 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.

like image 816
SrAxi Avatar asked Apr 12 '17 15:04

SrAxi


2 Answers

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.

like image 71
Tyler Jennings Avatar answered Sep 16 '22 16:09

Tyler Jennings


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.

like image 33
Lalith kumar Avatar answered Sep 18 '22 16:09

Lalith kumar