Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2 APP_INITIALIZER Execution order / async issue

Tags:

angular

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!

like image 917
josh-sachs Avatar asked Mar 03 '17 06:03

josh-sachs


1 Answers

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

like image 180
josh-sachs Avatar answered Nov 03 '22 00:11

josh-sachs