简体   繁体   中英

Angular service constructor is being triggered before APP_INITIALIZER can finish

I seem to have a race condition between a root provided service and APP_INITIALIZER in my app, and I'm not sure how to fix it.

I want to ensure the APP_INITIALIZER finishes first, as the service being triggered relies on an AppConfig that the APP_INITIALIZER is supposed to set up. Since the constructor is triggering first it's using an undefined value and throwing an error.

I have simplified the implementation of AppInitService, but the principal is the same. I want the promise returned by init to resolve before the UserSerialService constructor is called. Currently the constructor is being called first and is causing an error, which means the init promise never completes.

Service being constructed

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

  public userSerial$: BehaviorSubject<string>;
  private _urlPrefix = '/cldflt/user/api/user/my-serial';

  constructor(private _configService: AppConfigService,
              private _http: HttpClient) {
    this.getUserSerial$().pipe(take(1)).subscribe(res => {
      this.userSerial$ = new BehaviorSubject<string>(res.body.serial)
    })
  }

  public getUserSerial$ = (): Observable<HttpResponse<Serial>> => {
    const url = this._configService.getConfig().baseUrl + `${this._urlPrefix}`;
    return this._http.get<any>(url, { observe: 'response' });
  }
}

NgModule

export function initApp(appInitService: AppInitService) {
  console.log("InitApp called");
  return () => appInitService.init();

}

@NgModule({
  declarations: [
  ],
  imports: [
  ],
  entryComponents: [
  ],
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: initApp,
      deps: [AppInitService],
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

AppInitService

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

  constructor(private _configService: AppConfigService,
              private _splitService: SplitIOService) { }

  init(): Promise<void>{
    console.log("InitApp called");
    return Promise.resolve().then(() => {
      console.log("Promise complete");
    });
  }
}

I would propose you to refactor your code to handle asynchronicity in right way, in that case you won't care about orde of execution;

class AppConfigService {
  config$ = new ReplaySubject<Configuration>(1);
  setConfig(config: Configuration) {this.config$.next(config)}
  getConfig(): Observable<Configuration> {return this.config$}
}
class UserSerialService {
  // ...
  public getUserSerial$ = (): Observable<HttpResponse<Serial>> => {
    return this._configService.getConfig().pipe(switchMap(({baseUrl}) => 
          this._http.get<any>(url, { observe: 'response' })
    );

  }
}

Can you use a Subject to emit a signal when the initialization is done and subscribe in the other service and chain the two observables together by mergeMap etc?

you could inject your _splitService later, so it would be constructed later as well as its dependency UserSerialService

export class AppInitService {

  constructor(private _configService: AppConfigService,
              private injector: Injector) { }

  init(): Promise<void>{
    console.log("InitApp called");
    return Promise.resolve().then(() => {
      console.log("Promise complete");
    });
  }

  doSomething() {
     const splitService = this.injector.get(SplitIOService);
     splitService.youAreNoLongerAProblem();
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM