Why is my @Injectable service created more than once?

Stefan Falk

Note: Please scroll down to the "Update" as the issue was boiled down to a @Injectable service being instanciated more than once.

I have a resolver that loads businesses:

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any {
  this._logger.debug('Loading application data.');
  return forkJoin([
    this._userService.getMe(),
    this._businessService.getAll()
  ]).pipe(
    tap(([user, businesses]) => {
      this._appState.user = user;
      this._appState.businesses = businesses;
    }),
    finalize(() => this._logger.debug('Application data loaded.'))
  );
}

There is an ApplicationState that has two BehaviorSubject members _business and _businesses:

constructor() {
  this.businessChange = this._business.asObservable();
  this.businessesChange = this._businesses.asObservable();
}

set business(business: BusinessModel) {
  console.log(business);
  this._business.next(business);
}

get business(): BusinessModel {
  return this._business.getValue();
}

set businesses(value) {
  this._businesses.next(value);
  console.log(this.businesses);
}

get businesses(): Array<BusinessModel> {
  return this._businesses.getValue();
}

As you can see, businesses of said state gets set in the resolver. business however depends on the route and gets set in a (lazy-loaded) module depending on that route:

ngOnInit() {

  this._appState.businessesChange.subscribe(
    (b) => {
      console.log('businessesChange');
      console.log(b);
    }
  );

  this._subscriptions.add(
    this._activatedRoute.params
      .subscribe((params) => {
        this._routeParams = params;
        const businessId: number = parseInt(params['businessId'], 10);
        const businesses = this._appState.businesses;
        console.log(`businessId ${businessId}`);
        console.log('available businesses');
        console.log(businesses);
        this._appState.business = businesses.find(b => b.id === businessId);
      })
  );
}

This is the relevant log output:

2019-08-28T09:11:04.989Z DEBUG [..~df7ced28.js:428] Loading application data.
ngx-logger.js:251 2019-08-28T09:11:04.992Z DEBUG [main.js:398] Fetching /me
application.state.ts:56 Set businesses:
application.state.ts:58 (3) [{…}, {…}, {…}]
ngx-logger.js:251 2019-08-28T09:11:06.781Z DEBUG Application data loaded.
businesses.component.ts:43 businessesChange
businesses.component.ts:44 []
businesses.component.ts:47 businessId 4
businesses.component.ts:48 available businesses
businesses.component.ts:49 []
application.state.ts:46 Set business:
application.state.ts:47 undefined
ngx-logger.js:251 2019-08-28T09:11:08.386Z DEBUG Place is null. No action.

As you can see, businesses gets set and contains three elements. Then we see that Application data loaded. from the resolver. After that, we see the _activatedRoute.params subscription stating that the route reveals a businessId of 4 but then.. this._appState.businesses is all of a sudden an empty list - the initial value of the BehaviorSubject even though we printed that out a few moments earlier.

Since I am logging/debuggin the set methods as well, I can see that this value does not get changed after being set to that list of three elements.

I have no idea what's going on here. What could possibly cause this?


What I already tried:

  • Restart the server
  • Use the debugger (but I printf-debug anyway)

Update

Okay I have boiled the issue down to the ApplicationState being instanciated more than once:

By giving the ApplicationState a simple ID (timestamp):

constructor() {
  this.businessChange = this._business.asObservable();
  this.businessesChange = this._businesses.asObservable();
  this.timestamp = moment().format('MM.SS SSS');
  console.log(`${this.timestamp} ApplicationState created`);
}

and changing the log-output in the set and get methods, I get this:

08.09 095 ApplicationState created
ngx-logger.js:251 2019-08-28T09:51:16.098Z DEBUG Loading application data.
ngx-logger.js:251 2019-08-28T09:51:16.099Z DEBUG [main.js:398] Fetching /me
:4200/#/businesses/4/calendar:1 This site does not have a valid SSL certificate! Without SSL, your site's and visitors' data is vulnerable to theft and tampering. Get a valid SSL certificate before releasing your website to the public.
application.state.ts:60 08.09 095 Set businesses:
application.state.ts:62 (3) [{…}, {…}, {…}]
application.state.ts:38 08.14 145 ApplicationState created
ngx-logger.js:251 2019-08-28T09:51:16.161Z DEBUG Application data loaded.
businesses.component.ts:43 businessesChange
businesses.component.ts:44 []
businesses.component.ts:54 businessId 4
businesses.component.ts:55 available businesses
businesses.component.ts:56 []
application.state.ts:50 08.14 145 Set business:
application.state.ts:51 undefined
ngx-logger.js:251 2019-08-28T09:51:16.178Z DEBUG Place is null. No action.
calendar.component.ts:105 

So there is a 08.09 095 (A) and a 08.14 145 (B) instance of ApplicationState.

A gets the businesses set but further B is used apparently.

I checked, ApplicationState gets imported only once in my ApplicationModule:

providers: [
  // Services
  ApplicationState,
  ApiServices,
  ApplicationResolver,
]

So why is this happening? Shouldn't this be Singleton? Is it because this goes over a lazy-loaded module?

But most importantly: Why did this work for the last few weeks?

I also added providedIn by now:

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

but it still behaves the same.

Stefan Falk

Okay, the answer why this happened was apparently due to lazy loading. It seems to have something to do how the dependency tree gets build or something like that.

I do not know the details and I would accept any anser which is more detailed about this topic.

Anyway, I thought it should be enough to provide the service in a parent module. Because it makes sence, doesn't it?

Turns out that this really has to be provided in the app.module.ts. I have another module, called application.module.ts (should not be confused) where I provided ApplicationState. application.module.ts is lazy loaded but also has some routes which are again lazy loaded e.g. businesses.module.ts. So my hierarchy looks something like this:

app.module.ts
|_ application.module.ts  // lazy, providers: [ApplicationState]
|  |_ businesses.ts       // lazy, uses ApplicationState too
|_ public-pages.module.ts // not lazy

and what I had to change was to set

providers: [ApplicationState]

in app.module.ts.

This is not ideal imo but since lazy loading works like this I'll have to deal with it.

Apparently this is a known issue (https://github.com/angular/angular/issues/12869) but there is also a possible solution on stackoverflow: https://stackoverflow.com/a/41293784/826983

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Why is my code printing more than once?

Why wont my reverse LinkedList method not work more than once?

Why does my v-select render more than once?

Why is my signal handler not invoked more than once here?

Users being created more than once

Why's my response being executed more than once with the correct data instead of just only once with useEffect()?

Why is my injectable angular service class not saving class variables as expected?

Python is Not running my For Loop more than once

why is this controller function called more than once

Why does this rule run more than once?

Why does WriteFile not run more than once?

why javascript refuses to work more than once?

Why do I need to @import my SCSS files more than once to prevent errors?

Why is my integer value increasing more than once in Unity3D

Why does my jquery onclick function toggle on and off, if I run initialization more than once?

Why is my do/while loop in c++ not allowing me to input 'amount' more than once?

Why does my javascript prevent me from selecting a radio button more than once?

Why do I have to click more than once for my function works?

Enum *seems* to be initialized more than once, the constructor is called more than once. If I’m right, then why?

Why are my Service Fabric actors using more disk space than expected?

what is the purpose of registering the same service more than once?

"Scoped" service's constructor getting called more than once

My function is running more than once when I resize the page

How do I use my jQuery slidetoggle more than once?

Why exhausted generators raise StopIteration more than once?

Why Recyclerview adapter onBindViewHolder is called more than once?

Why can't a Traversable visit its elements more than once?

Why does keyframe animation repeat more than once?

Why might one load a library more than once in an R script?