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:
StateService
was located in the CoreModule
and provided in root
. The CoreModule
is only imported in the AppModule
.Bubble03StateService
was located in a lazy loaded Module and also provided in root
.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.
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...
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? 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.