Search code examples
angularangular8behaviorsubject

Angular8: BehaviourSubject partially not working when extracting to another Service


I encountered a really strange issue: in my application I followed the principle of having a state service handling all states. As the app is growing, I decided to create sub-state services. I extracted all related functions to a dedicatet service. Among others, this part was moved from state.service.ts to bubble03-state.service.ts:

@Injectable({
  providedIn: 'root'
})
export class Bubble03StateService {

  constructor() { }

  /* tslint:disable:rule1 member-ordering */ 

  // ...

  public currentVisibleEFSubcontainer$ = new BehaviorSubject<string>('');
  private currentSubjectValue = '';
  updateCurrentVisibleEFSubcontainer(newVisibleContainer: string): void {
    console.log(`updating with value ${newVisibleContainer}`);
    if (newVisibleContainer !== this.currentSubjectValue) {
      this.currentVisibleEFSubcontainer$.next(newVisibleContainer);
      this.currentSubjectValue = newVisibleContainer;
    } else {
      this.currentVisibleEFSubcontainer$.next('');
      this.currentSubjectValue = '';
    }
  }

}

The method updateCurrentVisibleEFSubcontainer() is called from two locations, one is a component, one is another service. Now the strange part: as soon as I moved this code to the new sub-state service, only one call triggers the subscriptions, while the other call does not. I can't really explain why, since its the exact same code, just in another place. All other moved functions work just fine. Following the two calls:

in a component

onClickRowElement(): void {
  if (this.rowElementAmount > 0) {
    this.bubble03State.updateCurrentVisibleEFSubcontainer(this.rowElementTitle);
    this.rowElementClicked.emit(this.rowElementTitle);
  }
}

in another service

chart.options.data[0].click = (e) => {
  this.explodePie(e);
  this.bubble03State.updateCurrentVisibleEFSubcontainer(e.dataPoint.name);
};

the subscription

this.bubble03State.currentVisibleEFSubcontainer$.subscribe(
  newVisibleEFContainer => {
    this.handleExpandableSubContainer(newVisibleEFContainer);
    console.log(`newly visible ${newVisibleEFContainer}`);
  }
);

If the method is called a few times from the component, following output gets logged

> updating with value Kontoführung 
> newly visible Kontoführung updating
> with value Versand & Bearbeitung 
> newly visible Versand & Bearbeitung
> updating with value Versand & Bearbeitung 
> newly visible

However, calling the methods from the service results in following log

> updating with value Kontoführung
> updating with value Versand & Bearbeitung
> updating with value Buchungsgebühren

As soon as I put the method and its corresponding BehaviourSubject back into the main service, everything works as intended. What could possibly cause this behaviour? There is no difference between the state services, except the name. Both are provided in root.

EDIT:

For clarification, the initial situation when posting the question was following:

  • The StateService was located in the CoreModule and provided in root. The CoreModule is only imported in the AppModule.
  • The Bubble03StateService was located in a lazy loaded Module and also provided in root.
  • The Bubble03StateService is used in the lazy loaded module and in a third service, which is also located in the CoreModule and provided in root, while the StateService is used in every module.

Following Richard Dunn's suggestion, I relocated the Bubble03StateService and tried a few things. It is now located in the CoreModule too.

  • When I remove the provideIn: 'root' and put both services in the providers-Array of the AppModule, the regular StateService still works fine, but the Bubble03StateService throws an NullInjectorError: no providers for...
  • Reinserting the provideIn: 'root' and removing from AppModule, the services are loaded, but something happens I do not understand: In the constructor of the services, I create a random number and log it whenever calling a function of the services. When putting the BehaviourSubject and its corresponding update method in the StateService, the random number is always the same, no matter who calls the method (third Service and lazy loaded component). But when I put it back in the Bubble03StateService (which at this point is in all ways identical to the StateService, except its name), the random number is different when called from the third Service and from the lazy loaded component. This confuses me even more, since now all three services from CoreModule should be singletons. If not, why is the StateService a singleton and the Bubble03StateService not, when they are declared, provided and located in the same way?

Solution

  • I finally found the cause for the issue.

    Somehow, while auto-importing the service inside the other service, the auto-import function added the extension .js to the import statement. This is really hard to catch, since it is not a thing you'll usually check explicitly.

    While there isn't even a .js File in this location, Angular somehow understands this and imports the right class / service / component etc from the .ts-File. BUT it creates a new instance of the service, annihilating the principle of singletons.

    So in the end, removing the .js extension from the import was all that had to be done.