15

I've got 2 APP_INITIALIZER providers... the first makes an HTTP request to get environment information.

The second uses the environment info to authorize the user against an OIDC Authority server endpoint (obtained from environment call).

It seems like, despite making the environment service a dependency of the authorization service, the authorization service's APP_INITIALIZER factory function is called before the environment call is completed.

{ provide: APP_INITIALIZER, multi: true, useFactory: EnvironmentFactory, deps: [] }
{ provide: APP_INITIALIZER, multi: true, useFactory: AuthorizationFactory, deps: [EnvironmentProvider] }

Both factories provided to APP_INITIALIZER are of the signature:

Factory() { return () => Promise; }

The result is that the authorization call submits to undefined instead of a proper URL.

I've thought of combining the factories - but they are in two different modules so it feels messy. Guidance appreciated!

josh-sachs
  • 1,749
  • 2
  • 15
  • 19
  • Please provide more code. I guess the `AuthorizationFactory` doesn't have a way to know when `EnvironmentFactory` received the response. – Günter Zöchbauer Mar 03 '17 at 06:58
  • 6
    I'm not sure what else I could provide. I understand that AuthorizationFactory doesn't know when EnvironmentFactory promise has resolved, I guess that is basically the spirit of the question. How to make one APP_INITIALIZER wait on another to resolve. I would prefer not to have a factory that does both internally if possible... e.g. EnvironmentAndAuthorizationFactory() – josh-sachs Mar 03 '17 at 15:13
  • I encountered the same issue recently and I solved it using a custom APP_INITIALIZER that ensure order of initialization. I factored this custom code into a reusable library. Feel free to check, use, and give some feedback : https://www.npmjs.com/package/ngx-ordered-initializer – Fabian Vilers Dec 17 '20 at 11:43

1 Answers1

5

I ended up injecting the resolved EnvironmentProvider into the AuthorizationFactory.

I added an observable to the EnvironmentProvider that emits any time the Authority value changes.

{ provide: APP_INITIALIZER, multi: true, 
  useFactory: EnvironmentFactory, deps: [EnvironmentProvider] }

{ provide: APP_INITIALIZER, multi: true, useFactory: AuthorizationFactory, 
  deps: [AuthorizationProvider, EnvironmentProvider] }


export function AuthorizationFactory (auth: AuthorizationProvider, env: EnvironmentService) { 
    return new Promise((resolve, reject) => env.Authority$()
        // don't emit until authority provider has a value
       .skipWhile(authority => !authority)
        // dispatch the auth command to ngrx/store.
       .do(() => store.dispatch({ type: 'AuthorizeIdentity' }))
        // switch to observe identity state
       .switchMap(() => store.select('Identity'))
        // don't emit until there is an identity (async authorization complete).
       .skipWhile(identity => !identity)
        // finish.
       .take(1)
       .subscribe(resolve, reject)
    });
}

I use a ReplaySubject(1) as the source for env.Authority$(). This ensures that the observable returned always emits upon subscription by the AuthorizationFactory (e.g. if the Authority was resolved prior to the AuthorizationFactory subscribing).

Anyone who comes across this wondering why I'm not using toPromise()... I think there is some issue (I've submitted for review here). https://github.com/Reactive-Extensions/RxJS/issues/1429

josh-sachs
  • 1,749
  • 2
  • 15
  • 19
  • I think you should return a function that returns a promise instead of a promise directly. Thanks for the idea! – alexseik Jul 25 '17 at 19:01
  • 6
    https://medium.com/@gmurop/managing-dependencies-among-app-initializers-in-angular-652be4afce6f found this post might a better solution – Vincent Feb 25 '19 at 15:29
  • For anyone, who might need a quick solution: https://stackoverflow.com/a/57585756/1611569. – Halfist Nov 18 '20 at 11:34