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.

Community
  • 1
  • 1
SrAxi
  • 19,787
  • 11
  • 46
  • 65
  • I am adding Updates with my desperate attempts... Please end my torture! xD – SrAxi Apr 13 '17 at 09:42
  • Hi, have you resolved this issue by now? – Bones and Teeth Jul 20 '17 at 19:51
  • @BonesandTeeth Yes! Thanks! :D – SrAxi Jul 20 '17 at 20:16
  • 1
    Can you tell me what you did o make it work? I am also facing the same issue.... – Bones and Teeth Jul 20 '17 at 20:39
  • @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! – SrAxi Jul 21 '17 at 07:04
  • @SrAxi 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. – bharath muppa Oct 24 '17 at 09:05

3 Answers3

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.

Community
  • 1
  • 1
Tyler Jennings
  • 8,761
  • 2
  • 44
  • 39
  • Or `ReplaySubject`, which doesn't require an initial default value. – jonrsharpe Apr 12 '17 at 16:04
  • Correct, either will replace the most recent value. – Tyler Jennings Apr 12 '17 at 16:06
  • @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`. – SrAxi Apr 12 '17 at 16:17
  • @SrAxi you don't want to remove `asObservable`, you still want to expose the observable to the consumers. – jonrsharpe Apr 12 '17 at 16:18
  • You shouldn't need to remove `asObservable()`, let me post a more thorough example of `BehaviorSubject`. – Tyler Jennings Apr 12 '17 at 16:19
  • 1
    @TylerJennings Thanks again! But, is still only working 1 time, not when I call `updateIBOsNavigation()`. – SrAxi Apr 12 '17 at 16:24
  • Is an exception happening anywhere? If the Observable encounters an error, it will terminate it for all subscribers. – Tyler Jennings Apr 12 '17 at 16:30
  • @TylerJennings I tried this way of implementing `BehaviorSubject`: http://stackoverflow.com/questions/36404541/behavioursubject-in-angular2-how-it-works-and-how-to-use-it . Nothing happened. – SrAxi Apr 12 '17 at 16:30
  • @TylerJennings No errors, just `subscribe()` not triggering – SrAxi Apr 12 '17 at 16:31
  • 1
    Interesting. It sounds like everything should be working. You say the `subscribe()` only works once and then not again? – Tyler Jennings Apr 12 '17 at 16:36
  • @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! – SrAxi Apr 13 '17 at 07:49
  • 2
    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. – Tyler Jennings Apr 13 '17 at 12:08
  • 6
    @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! – SrAxi Apr 13 '17 at 12:58
  • 1
    The 'edit' part should be at the top, because that's what actually helps :) – sr9yar Jan 24 '18 at 11:57
  • My issue as well was providing the service NOT on the root module, but getting there I got to know ReplaySubject :) – Mac_W Sep 20 '18 at 10:50
  • 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 ! – JLavoie Oct 26 '18 at 15:48
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://stackoverflow.com/a/38034298/5730167.

Lalith kumar
  • 103
  • 1
  • 6
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.

Alex Bravo
  • 1,601
  • 2
  • 24
  • 40