3

I have an Angular 5 application with an HTTP interceptor that calls another service whenever it intercepts a request.
I want to make the service configurable using a config file, so I tried using a service that loads the configuration asynchronously during app initialization (using the APP_INITIALIZER hook).
The problem is, that the service I want to use is being instantiated before the configuration service finishes loading the configuration from the config file.

Is there a way to defer the service instantiation until after the configuration service finishes loading the configuration?


Code

You can see the full example in stackblitz.
The most relevant parts are below:

interceptor.service.ts

export class InterceptorService implements HttpInterceptor {
  constructor(private dependency: InjectedDependencyService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // use InjectedDependencyService to do something on every intercepted request
    const userId = this.dependency.getUserId();
    console.log("user id:", userId);
    return next.handle(request);
  }
}

injected-dependency.service.ts

export class InjectedDependencyService {
  userId;

  constructor(private initializer: InitializerService) {
    this.userId = initializer.config.id;
  }

  // Do something that relies on the configuration being initialized
  getUserId() {
    console.log("Is configuration initialized?", !!this.initializer.config.id)
    return this.userId;
  }
}

initializer.service.ts

@Injectable()
export class InitializerService {
  config = {};

  constructor(private http: HttpClient) { }

  initialize() {
    // TODO replace with link to a config file
    const url = "https://jsonplaceholder.typicode.com/users/1";
    return this.http.get(url).toPromise().then(response => {
      this.config = response;
      console.log("App initialized", this.config);
    }, e => console.error(e));
  }
}

app.module.ts

...
export function initializerServiceFactory(initializerService: InitializerService) {
  return () => initializerService.initialize();
}
...
providers: [
  InitializerService,
  InjectedDependencyService,
  {
    provide: APP_INITIALIZER,
    useFactory: initializerServiceFactory,
    deps: [InitializerService],
    multi: true
  },
  {
    provide: HTTP_INTERCEPTORS,
    useClass: InterceptorService,
    multi: true
  }
]
...

The result

console output

Oram
  • 1,589
  • 2
  • 16
  • 22
  • In your interceptor, check if your config is loaded, if not, add your api call to a subscriber that will wait for config to load. – Florian Jun 07 '19 at 12:08
  • 1
    You have to return valid Promise from initializerService.initialize() – isevcik Jun 07 '19 at 12:33
  • I am not sure if I get this. But I think if you are setting an interceptor, this first request to get the config will pass through it, before getting the config itself – Doguita Jun 07 '19 at 12:39
  • @Florian as Doguita suggested, the problem is that the interceptor intercepts the call from the initializerService and then instantiates the services. See accepted answer for a solution. – Oram Jun 10 '19 at 06:49
  • @isevcik what's not valid with the promise created from `Promise.all`? – Oram Jun 10 '19 at 06:49

1 Answers1

2

InitializerService calls HttpClient which triggers InterceptorService which in turn calls getUserId(). You want initialization to finish before any interceptors are used.

You can use HttpBackend to bypass all interceptors.

@Injectable()
export class InitializerService {
  config = {};

  constructor(private backend: HttpBackend) { }

  initialize() {
    const http = new HttpClient(this.backend);
    const url = "https://jsonplaceholder.typicode.com/users/1";
    return http.get(url).toPromise().then(response => {
      this.config = response;
      console.log("App initialized", this.config);
    }, e => console.error(e));
  }
}
Reactgular
  • 52,335
  • 19
  • 158
  • 208