Angular APP_INITIALIZER 模块无法正常工作
Angular APP_INITIALIZER in module not working properly
我有一个 auth 模块,它在应用初始化之前导入并设置 msal 配置。我正在使用 APP_INITIALIZER 来阻止应用程序的初始化并获取 MSAL-ANGULAR 的配置。
该问题仅与 Firefox 有关。该应用程序在 Chrome、Egde、Safari 上运行良好,但在 Firefox 上运行不佳。在不同于 Firefox 的所有其他浏览器上,APP_INITIALIZER 会阻止启动,直到加载 config.json。
我注意到 MSALInstanceFactory 在 initializerFactory 函数之前被调用。
我在 auth.module
中使用的代码
export function initializerFactory(env: ConfigService, configUrl: string): any {
// APP_INITIALIZER, angular doesnt starts application untill it completes loading config.json
const promise = env.init(configUrl).then(val => {});
return () => promise;
}
export function loggerCallback(logLevel: LogLevel, message: string): void {
console.log(message);
}
export function MSALInstanceFactory(configService: ConfigService, constant: SecurityConstants): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: configService.getSettings('clientId'),
redirectUri: configService.getSettings('redirectUri'),
authority: configService.getSettings('authority'),
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE,
},
system: {
loggerOptions: {
loggerCallback,
logLevel: LogLevel.Info,
piiLoggingEnabled: false,
},
},
});
}
export function MSALInterceptorConfigFactory(configService: ConfigService, constant: SecurityConstants): MsalInterceptorConfiguration {
const protectedResourceMap = new Map<string, Array<string>>();
protectedResourceMap.set('/*/', [configService.getSettings('scope')]);
return {
interactionType: InteractionType.Redirect,
protectedResourceMap,
};
}
export function MSALGuardConfigFactory(configService: ConfigService, constant: SecurityConstants): MsalGuardConfiguration {
return {
interactionType: InteractionType.Redirect,
authRequest: {
scopes: [configService.getSettings('scope')],
},
};
}
@NgModule({
imports: [MsalModule]
})
export class AuthModule {
static forRoot(configFile: string) {
return {
ngModule: AuthModule,
providers: [
ConfigService,
MsalService,
{ provide: AUTH_CONFIG_URL_TOKEN, useValue: configFile },
{
provide: APP_INITIALIZER,
useFactory: initializerFactory,
deps: [ConfigService, AUTH_CONFIG_URL_TOKEN, SecurityConstants],
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true,
},
{
provide: MSAL_INSTANCE,
useFactory: MSALInstanceFactory, // this is initiated before APP_INITIALIZER
deps: [ConfigService, SecurityConstants],
},
{
provide: MSAL_GUARD_CONFIG,
useFactory: MSALGuardConfigFactory,
deps: [ConfigService, SecurityConstants],
},
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: MSALInterceptorConfigFactory,
deps: [ConfigService, SecurityConstants],
},
MsalGuard,
MsalBroadcastService,
],
};
}
}
配置服务中的代码
@Injectable({
providedIn: 'root',
})
export class ConfigService {
config: any;
private http: HttpClient;
constructor(private readonly httpHandler: HttpBackend) {
this.http = new HttpClient(httpHandler);
}
init(endpoint: string): Promise<boolean> {
let promise = new Promise<boolean>((resolve, reject) => {
this.http
.get<ConfigResponse>(endpoint)
.pipe(map((res) => res))
.subscribe(
(value) => {
this.config = value;
},
(error) => {
reject(false);
},
() => {
resolve(true);
}
);
});
return promise;
}
getSettings(key?: string | Array<string>): any {
if (!key || (Array.isArray(key) && !key[0])) {
return this.config;
}
if (!Array.isArray(key)) {
key = key.split('.');
}
let result = key.reduce((acc: any, current: string) => acc && acc[current], this.config);
return result;
}
}
代码在app.module
@NgModule({
declarations: [AppComponent],
imports: [
AuthModule.forRoot('assets/config.json'),
CommonModule,
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
RouterModule
],
bootstrap: [AppComponent, MsalRedirectComponent],
})
export class AppModule {}
我发现 APP_INITIALIZER 不能保证您的配置服务首先完成。此处也报告了此问题:https://github.com/angular/angular/issues/23279,遗憾的是仍未解决。
最初 MSAL_Instance 在此之前加载,为了解决这个问题,我更改了 main.ts bootstrap 模块。
// main.ts
function bootstrapFailed(val) {
document.getElementById('bootstrap-fail').style.display = 'block';
console.error('bootstrap-fail', val);
}
fetch('config.json')
.then(response => response.json())
.then(config => {
if (!config || !config['isConfig']) {
bootstrapFailed(config);
return;
}
// Store the response somewhere that your ConfigService can read it.
window['tempConfigStorage'] = config;
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(bootstrapFailed);
})
.catch(bootstrapFailed);
我也遇到了 APP_INITIALIZER 的问题。
我发现如果我替换 app-routing.module 的这一行(在 documentation 中提到)
initialNavigation: !isIframe ? 'enabled' : 'disabled'
有了这个
initialNavigation: !isIframe ? 'enabledNonBlocking' : 'disabled'
问题已解决!
您也可以使用 msal 的 BrowserUtils 代替 isIframe 变量。
initialNavigation: !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup() ? 'enabledNonBlocking' : 'disabled'
我有一个 auth 模块,它在应用初始化之前导入并设置 msal 配置。我正在使用 APP_INITIALIZER 来阻止应用程序的初始化并获取 MSAL-ANGULAR 的配置。 该问题仅与 Firefox 有关。该应用程序在 Chrome、Egde、Safari 上运行良好,但在 Firefox 上运行不佳。在不同于 Firefox 的所有其他浏览器上,APP_INITIALIZER 会阻止启动,直到加载 config.json。 我注意到 MSALInstanceFactory 在 initializerFactory 函数之前被调用。
我在 auth.module
中使用的代码export function initializerFactory(env: ConfigService, configUrl: string): any {
// APP_INITIALIZER, angular doesnt starts application untill it completes loading config.json
const promise = env.init(configUrl).then(val => {});
return () => promise;
}
export function loggerCallback(logLevel: LogLevel, message: string): void {
console.log(message);
}
export function MSALInstanceFactory(configService: ConfigService, constant: SecurityConstants): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: configService.getSettings('clientId'),
redirectUri: configService.getSettings('redirectUri'),
authority: configService.getSettings('authority'),
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE,
},
system: {
loggerOptions: {
loggerCallback,
logLevel: LogLevel.Info,
piiLoggingEnabled: false,
},
},
});
}
export function MSALInterceptorConfigFactory(configService: ConfigService, constant: SecurityConstants): MsalInterceptorConfiguration {
const protectedResourceMap = new Map<string, Array<string>>();
protectedResourceMap.set('/*/', [configService.getSettings('scope')]);
return {
interactionType: InteractionType.Redirect,
protectedResourceMap,
};
}
export function MSALGuardConfigFactory(configService: ConfigService, constant: SecurityConstants): MsalGuardConfiguration {
return {
interactionType: InteractionType.Redirect,
authRequest: {
scopes: [configService.getSettings('scope')],
},
};
}
@NgModule({
imports: [MsalModule]
})
export class AuthModule {
static forRoot(configFile: string) {
return {
ngModule: AuthModule,
providers: [
ConfigService,
MsalService,
{ provide: AUTH_CONFIG_URL_TOKEN, useValue: configFile },
{
provide: APP_INITIALIZER,
useFactory: initializerFactory,
deps: [ConfigService, AUTH_CONFIG_URL_TOKEN, SecurityConstants],
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true,
},
{
provide: MSAL_INSTANCE,
useFactory: MSALInstanceFactory, // this is initiated before APP_INITIALIZER
deps: [ConfigService, SecurityConstants],
},
{
provide: MSAL_GUARD_CONFIG,
useFactory: MSALGuardConfigFactory,
deps: [ConfigService, SecurityConstants],
},
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: MSALInterceptorConfigFactory,
deps: [ConfigService, SecurityConstants],
},
MsalGuard,
MsalBroadcastService,
],
};
}
}
配置服务中的代码
@Injectable({
providedIn: 'root',
})
export class ConfigService {
config: any;
private http: HttpClient;
constructor(private readonly httpHandler: HttpBackend) {
this.http = new HttpClient(httpHandler);
}
init(endpoint: string): Promise<boolean> {
let promise = new Promise<boolean>((resolve, reject) => {
this.http
.get<ConfigResponse>(endpoint)
.pipe(map((res) => res))
.subscribe(
(value) => {
this.config = value;
},
(error) => {
reject(false);
},
() => {
resolve(true);
}
);
});
return promise;
}
getSettings(key?: string | Array<string>): any {
if (!key || (Array.isArray(key) && !key[0])) {
return this.config;
}
if (!Array.isArray(key)) {
key = key.split('.');
}
let result = key.reduce((acc: any, current: string) => acc && acc[current], this.config);
return result;
}
}
代码在app.module
@NgModule({
declarations: [AppComponent],
imports: [
AuthModule.forRoot('assets/config.json'),
CommonModule,
BrowserModule,
BrowserAnimationsModule,
HttpClientModule,
RouterModule
],
bootstrap: [AppComponent, MsalRedirectComponent],
})
export class AppModule {}
我发现 APP_INITIALIZER 不能保证您的配置服务首先完成。此处也报告了此问题:https://github.com/angular/angular/issues/23279,遗憾的是仍未解决。 最初 MSAL_Instance 在此之前加载,为了解决这个问题,我更改了 main.ts bootstrap 模块。
// main.ts
function bootstrapFailed(val) {
document.getElementById('bootstrap-fail').style.display = 'block';
console.error('bootstrap-fail', val);
}
fetch('config.json')
.then(response => response.json())
.then(config => {
if (!config || !config['isConfig']) {
bootstrapFailed(config);
return;
}
// Store the response somewhere that your ConfigService can read it.
window['tempConfigStorage'] = config;
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(bootstrapFailed);
})
.catch(bootstrapFailed);
我也遇到了 APP_INITIALIZER 的问题。
我发现如果我替换 app-routing.module 的这一行(在 documentation 中提到)
initialNavigation: !isIframe ? 'enabled' : 'disabled'
有了这个
initialNavigation: !isIframe ? 'enabledNonBlocking' : 'disabled'
问题已解决!
您也可以使用 msal 的 BrowserUtils 代替 isIframe 变量。
initialNavigation: !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup() ? 'enabledNonBlocking' : 'disabled'