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:
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.
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.
Comments