APP_INITIALIZER在出厂前未触发

时间:2018-10-24 18:53:29

标签: angular typescript dependency-injection

我正在使用APP_INITIALIZER来加载特定于环境的变量。我需要在authConfigFactory中使用这些变量,但是工厂会在APP_INITIALIZER完成应用程序配置之前保持加载状态。

我正在使用此库:https://github.com/manfredsteyer/angular-oauth2-oidc

我想在我的身份验证配置工厂中使用APP_CONFIG.authConfig.allowedUrls的值。如何确保它在auth工厂之前先设置配置。

我在工厂遇到此错误:Cannot read property 'authConfig' of undefined

app.module.ts

providers: [
    AppConfigService,
    {
      provide: APP_INITIALIZER,
      useFactory: (config: AppConfigService) => () => config.load(),
      multi: true,
      deps: [AppConfigService]
    },
    {
      provide: OAuthModuleConfig,
      useFactory: authConfigFactory
    }
]

app.config.ts

export let APP_CONFIG: AppConfig;

@Injectable()
export class AppConfigService {
  constructor(
    private injector: Injector
  ) {}

  config = null;

  public load() {
    const http = this.injector.get(HttpClient);

    return http
      .get('../assets/config/config.json')
      .pipe(
        tap(returnedConfig => {
          const t = new AppConfig();
          APP_CONFIG = Object.assign(t, returnedConfig);
        })
      )
      .toPromise();
  }

}

auth-config-factor

export function authConfigFactory(): OAuthModuleConfig {
  return {
    resourceServer: {
      allowedUrls: APP_CONFIG.authConfig.allowedUrls,
      sendAccessToken: true
    }
  };
}

4 个答案:

答案 0 :(得分:1)

我以前遇到过这个问题,没有运气就尝试了很多可能性,唯一的解决方案是我使用了ngrx / store

在app.config.ts中,您可以调度操作以将配置保存在商店中,然后可以通过执行以下操作在其他服务中获取它:store.select()订阅它并进行控制

app.module.ts

providers: [
 AuthService, // oidc-client.ts where i need the config from json
 DataService,
 ConfigService,
 {
  provide: APP_INITIALIZER,
  useFactory: loadConfig,
  deps: [ConfigService],
  multi: true,
 },

config.service.ts

  import { HttpClient } from '@angular/common/http';
  import { Injectable } from '@angular/core';
  import { Store } from '@ngrx/store';
  import { IAppStore } from '../models/store.model';
  import * as ConfigActions from '../store/actions/config.actions';

  @Injectable()
   export class ConfigService {
   public config: any = {};

   constructor(private http: HttpClient, private store: Store<IAppStore>) {}

   getConfig(key: string): string {
     return this.config[key] || '';
    }
  public loadConfig() {
     return new Promise((resolve, reject) => {
      this.http.get('app-config.json').subscribe(
      (response) => {
        this.config = response;
        this.store.dispatch(new ConfigActions.AddConfig(response)); // dispatch action to update the store
        resolve(true);
      }
    );
  });
  }}

AuthService

  import { Log, User, UserManager, WebStorageStateStore } from 'oidc-client';
  ...
  @Injectable()
  export class AuthService {
  private _userManager: UserManager;
  public _user: User;
  constructor(
    private store: Store<IAppStore>
    private httpClient: HttpClient,
    private route: Router,
    private configs: ConfigService
  ) {
this.store.select('appConfig').subscribe((configdata) => {
  Log.logger = console;
  const config = {
  authority: configdata.stsAuthority,
  client_id: configdata.clientId,
  redirect_uri: `${configdata.clientRoot}/#/auth-callback#`,
  scope: 'openid profile fusionApi.full_access',
  response_type: 'id_token token',
  post_logout_redirect_uri: `${configdata.clientRoot}?postLogout=true`, // delet all stored tokens after logout
  userStore: new WebStorageStateStore({ store: window.localStorage }),
  automaticSilentRenew: true,
  silent_redirect_uri: `${configdata.clientRoot}/assets/html/silent-refresh-redirect.html`,
};
  this._userManager = new UserManager(config);
  this._userManager.getUser().then((user) => {
  if (user && !user.expired) {
    this._user = user;
  }
});
 ...
}

login(): Promise<any> {
 return this._userManager.signinRedirect();
}
...

答案 1 :(得分:1)

我有同样的问题。 OAuthModuleConfig需要同步设置。因此,需要在创建OAuthModuleConfig之前(在工厂中)加载设置。

我通过在引导AppModule之前加载设置来实现它。

Main.ts:

fetch('/assets/config.json')
.then(response => response.json())
.then(config => {

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic([{ provide: APP_SETTINGS, useValue: config }])
  .bootstrapModule(AppModule)
  .catch(err => console.error(err));
});

App.module.ts

在模块声明内:

....
providers: [
{ provide: OAuthModuleConfig, useFactory: authConfigFactory, deps: [APP_SETTINGS] },
],
bootstrap: [AppComponent]
.....


export function authConfigFactory(settings: AppSettings): OAuthModuleConfig {
return {
resourceServer: {
    allowedUrls: settings.protectedUrls,
    sendAccessToken: true,
   }
 };
}

答案 2 :(得分:0)

我有同样的问题并解决。

模块工厂:

import { InjectionToken } from '@angular/core';
import { HttpClient, HttpBackend } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { AuthorizationService } from './authorization.service';

export const AUTH_CONFIG = new InjectionToken<string>('auth.config.path', {
    factory: () => 'config.json',
  });


  export function CheckAuthorizationState(handler: HttpBackend, authService: AuthorizationService, path: string) {
    return async () => {
      const http = new HttpClient(handler);
      // get config for authorization
      await http.get(path)
        .pipe(
          map((response: any) => {
            authService.init(response);
          })
        ).toPromise();
      // check authorization
      await authService.checkAuthorization();
    };
  }

模块:

import { NgModule, APP_INITIALIZER, ModuleWithProviders, InjectionToken } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AuthorizationService } from './authorization.service';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthorizationGuardService } from './authorization-guard.service';
import { AuthorizationInterceptor } from './authorization-interpretator';
import { HttpBackend, HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { CheckAuthorizationState, AUTH_CONFIG } from './module-factories';




@NgModule({
  imports: [
    CommonModule
  ],
  providers: [
    AuthorizationService,
    AuthorizationGuardService,
    {
      provide: APP_INITIALIZER,
      useFactory: CheckAuthorizationState,
      deps: [HttpBackend, AuthorizationService, AUTH_CONFIG],
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthorizationInterceptor,
      multi: true
    }
  ],
  declarations: []
})

export class AuthorizationModule {
  static forRoot(path: string): ModuleWithProviders {
    return {
      ngModule: AuthorizationModule,
      providers: [
        { provide: AUTH_CONFIG, useValue: path }
      ]
    };
  }
}

这是我为您发布的授权库:https://www.npmjs.com/package/sso-authorization-oidc-client

答案 3 :(得分:0)

我今天才遇到此问题,以下是我如何解决此问题的方法。希望这可能对某人有所帮助。

这在某种程度上特定于angular-oauth2-oidc

export const oauthModuleConfigFactory = (config: AppConfig): OAuthModuleConfig => ({
    resourceServer: {
        allowedUrls: [config.apiUrl],
        sendAccessToken: true
    }
});

// OAuthModuleConfig is provided by OAuthModule as useValue
// and Angular resolves useValue providers before APP_INITIALIZER
// This may be a bug, since Angular still attempts to resolve the value
// even if it has been overriden with a useFactory as below
const fixForAppInitializers = ({ ngModule, providers = [] }: ModuleWithProviders<any>) => ({
    ngModule,
    providers: [...providers.filter((value: any) => value.provide !== OAuthModuleConfig)]
});

@NgModule({
    imports: [fixForAppInitializers(OAuthModule.forRoot())],
    providers: [
        AuthGuard,
        { provide: OAuthModuleConfig, useFactory: oauthModuleConfigFactory, deps: [APP_CONFIG] },
        ...
    ]
})
export class AuthModule { }

但这还不够,因为OAuthModule的{​​{1}}需要DefaultOAuthInterceptor,这需要首先加载OAuthModuleConfig,但是为了加载配置,我们需要使用app.config.json根据请求注入HttpClient,从而在下面抛出HTTP_INTERCEPTORS错误。

这是我解决该问题的方法:

config is not loaded