Angular 2: Observable / Subscription not triggering
Asked Answered
E

3

21

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.

Ezra answered 12/4, 2017 at 15:55 Comment(6)
I am adding Updates with my desperate attempts... Please end my torture! xDEzra
Hi, have you resolved this issue by now?Appointed
@BonesandTeeth Yes! Thanks! :DEzra
Can you tell me what you did o make it work? I am also facing the same issue....Appointed
@BonesandTeeth Of course, just read the answer and all the comments that follow. In my last comment, I explain how I solved my issue. Hope you solve it too! Cheers!Ezra
@Ezra I wonder to think of same issue while doing interceptor implementation { provide: HTTP_INTERCEPTORS, useClass: HttpClientInterceptor, multi: true, } how can i call this HttpClientInterceptor inside my component without adding to provider in component decorator.Sighted
R
46

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.

Repressive answered 12/4, 2017 at 16:4 Comment(16)
Or ReplaySubject, which doesn't require an initial default value.Cecum
Correct, either will replace the most recent value.Repressive
@TylerJennings Thanks! I tried with BehaviorSubject and removing asObservable(). It worked the first time (on init), but not each time I call the method. I am trying now with ReplaySubject.Ezra
@Ezra you don't want to remove asObservable, you still want to expose the observable to the consumers.Cecum
You shouldn't need to remove asObservable(), let me post a more thorough example of BehaviorSubject.Repressive
@TylerJennings Thanks again! But, is still only working 1 time, not when I call updateIBOsNavigation().Ezra
Is an exception happening anywhere? If the Observable encounters an error, it will terminate it for all subscribers.Repressive
@TylerJennings I tried this way of implementing BehaviorSubject: #36405041 . Nothing happened.Ezra
@TylerJennings No errors, just subscribe() not triggeringEzra
Interesting. It sounds like everything should be working. You say the subscribe() only works once and then not again?Repressive
@TylerJennings Yes, it works the first time with BehaviorSubject, but it's not after updateIBOsNavigation() is called. Is just that on init it enters subscrive() and then nothing else happens, I keep calling the service's method and subscribe() still doesn't trigger!Ezra
Is the Module containing this service provided ONLY in the very first root Module? Is there a chance that another module between the root module and ones these components live in create a new instance of the service? The example I provide in my answer, I'm using the exact same syntax in several places without issue.Repressive
@TylerJennings Incredible timing! I was about to post the answer myself. I just now made it work! And exactly, it's because of the service being included in a @Module between the AppModule and the final component. So, the type of Subject had nothing to do, it was the inclusion of the service: Had to be in root module = AppModule. If you want to add this to your question I can put it as the correct answer, if not I post an answer with the updated code. Anyways, you answer obviusly has my +1 and I have learned a lot researching this past 24h about types of Subject. Thanks!Ezra
The 'edit' part should be at the top, because that's what actually helps :)Gomuti
My issue as well was providing the service NOT on the root module, but getting there I got to know ReplaySubject :)Enchorial
Thanks for sharing and now it's fixed ! I was having the same problem: my submodules are sharing services with the root app module and all calls from the root components were not sent ! I confirm that you need to put in the providers the services that are used in the root module. In other words, you need to put the service in the providers of the most parent module who is using this service. And you can't inject the same service in the providers of your children module because that service would no longer be a singleton !Sewage
T
8

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://mcmap.net/q/466251/-angular-2-observable-subscription-not-triggering.

Tomcat answered 25/12, 2017 at 6:26 Comment(0)
P
3

In my case I was using the same service at parent module "app.module" and in child module as well. I Removed Service from Provider section in Child Module. It Worked.

Parker answered 27/3, 2020 at 14:39 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.