如何在APP_INITIALIZER中处理/通知用户不可恢复的异常?

net*_*tik 21 initialization typescript angular

我的Angular5应用程序在应用程序初始化期间从后端加载配置文件(APP_INITIALIZER).由于应用程序无法在没有它的情况下运行,我的目标是向用户显示无法加载配置的消息.

 providers: [ AppConfig,
        { provide: APP_INITIALIZER, useFactory: (config: AppConfig) => () => config.load(), deps: [AppConfig], multi: true },
        { provide: LocationStrategy, useClass: HashLocationStrategy},
        { provide: ErrorHandler, useClass: GlobalErrorHandler }]
Run Code Online (Sandbox Code Playgroud)

AppConfig班应该加载从应用程序加载之前的后端服务的配置文件:

@Injectable()
export class AppConfig {

    private config: Object = null;
    constructor(private http: HttpClient) {}

    public getConfig(key: any) {
        return this.config[key];
    }


    public load() {
        return new Promise((resolve, reject) => {
            this.http.get(environment.serviceUrl + 'config/config')
                .catch((error: any) => {                  
                    return Observable.throw(error || 'Server error');
                })
                .subscribe((responseData) => {
                    this.config = responseData;
                    this.config['service_endpoint'] = environment.serviceUrl;                  
                    resolve(true);
                });
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

全局异常处理程序:

@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
    constructor( private messageService: MessageService) { }
    handleError(error) {
        // the AppConfig exception cannot be shown with the growl message since the growl component is within the AppComponent
        this.messageService.add({severity: 'error', summary: 'Exception', detail: `Global Exception Handler: ${error.message}`});

        throw error;
    }

}
Run Code Online (Sandbox Code Playgroud)

如果无法加载配置文件,则会在全局异常处理程序(console.log()中未捕获的HTTPErrorResponse)中抛出,捕获并重新抛出异常,并且加载微调器永远挂起)

由于AppComponent没有加载(这是好的,因为没有配置就不能使用app)并且因为我的消息/"growl"组件是子组件AppComponent,我无法向用户显示消息.

有没有办法index.html在这个阶段在页面中显示消息?我不想将用户重定向到与index.html不同的.html页面,因为用户只需在error.html上重新加载/ f5.

小智 10

在 main.ts 中,我以这种方式更改了引导程序:

platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => {
    // error should be logged into console by defaultErrorLogger, so no extra logging is necessary here
    // console.log(err);
    // show error to user
    const errorMsgElement = document.querySelector('#errorMsgElement');
    let message = 'Application initialization failed';
    if (err) {
        if (err.message) {
            message = message + ': ' + err.message;
        } else {
            message = message + ': ' + err;
        }
    }
    errorMsgElement.textContent = message;
});
Run Code Online (Sandbox Code Playgroud)

任何发生的异常都会被捕获,并且消息被设置到一个 html 元素中。该元素在 index.html app-root 标签中定义,例如

<app-root><div id="errorMsgElement" style="padding: 20% 0; text-align: center;"></div> 
</app-root>
Run Code Online (Sandbox Code Playgroud)

如果引导程序成功,则标记的内容将替换为 angular。


use*_*875 5

我也没有为类似问题找到好的解决方案,但是作为一种解决方法,这是我的方法:

  • 用一个initialized变量扩展config类:

    @Injectable()
    export class AppConfig {       
        public settings: AppSettings = null;
        public initialized = false;
    
        public load() {
            return new Promise((resolve, reject) => {
                this.http.get(environment.serviceUrl + 'config')
                    .catch((error: any) => {                      
                        this.initialized = false;
                        resolve(error);
                        return Observable.throw(error || 'Server error');
                    })
                    .subscribe((responseData: any) => {
                        this.settings = responseData;
                        this.initialized = true;
                        resolve(true);
                    });
            });
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • app.component.html根据该变量显示应用程序或错误消息:

    <div *ngIf="!appConfig.initialized">
        <b>Failed to load config!</b>
        <!-- or <app-config-error-page> -->
    </div>
    <div *ngIf="appConfig.initialized" #layoutContainer >
        <!-- the app content -->
    </div>
    
    Run Code Online (Sandbox Code Playgroud)
  • 全局异常处理程序:

    @Injectable()
    export class GlobalErrorHandler implements ErrorHandler {
        constructor( private notificationService: NotificationService) { }
        handleError(error) {
            this.notificationService.error('Exception', `${error.message}`);
            return Observable.throw(error);
        }  
    }
    
    Run Code Online (Sandbox Code Playgroud)

当然,也可以将具体的错误消息/异常文本存储在config对象中,如果需要,可以在app.component / error.page.component中向用户显示。


Gar*_*ill 5

我提供的方法略有不同。在我看来,如果我们不能正确初始化它,我们希望 Angular 应用程序的引导程序继续,因为它无法正常工作。相反,我们只想向用户显示消息,然后停止。

背景:在我的初始化代码中,我必须按特定顺序发出两个异步请求:

  1. 我向我的“自己的”Web 服务器(提供 Angular 应用程序的服务器)发出 HTTP 请求,以获取托管我的后端 API的第二个Web 服务器的主机名。

  2. 然后我向第二个 Web 服务器发出请求以检索更多配置信息。

如果这些请求中的任何一个失败,那么我需要停止并显示一条消息,而不是 Angular 继续引导过程。

这是添加新的异常处理之前我的代码的简化版本(请注意,我更喜欢使用Promiseand async/await而不是Observable,但这并不是真正相关):

const appInitializer = (
  localConfigurationService: LocalConfigurationService,
  remoteConfigurationService: RemoteConfigurationService
): () => Promise<void> => {

  return async () => {
    try {
      const apiHostName = await localConfigurationService.getApiHostName();
      await remoteConfigurationService.load(apiHostName);
    } catch (e) {
      console.error("Failed to initialize the Angular app!", e);
    }
};

export const appInitializerProvider = {
  provide: APP_INITIALIZER,
  useFactory: appInitializer,
  deps: [LocalConfigurationService, RemoteConfigurationService],
  multi: true
};
Run Code Online (Sandbox Code Playgroud)

该代码会将错误记录到控制台,然后继续引导过程 - 这不是我们想要的。

要修改此代码以 (a) 显示一条消息,以及 (b) 在其轨道上停止引导程序,我在该console.error调用之后立即添加了以下 3 行:

      window.document.body.classList.add('failed');

      const forever = new Promise(() => { }); // will never resolve
      await forever;
Run Code Online (Sandbox Code Playgroud)

第一行简单地将类“失败”添加到文档<body>元素。稍后我们将看到这有什么影响。

其他两行awaitaPromise永远不会解决 - 换句话说,他们永远等待。这会导致 Angular 引导过程停滞,因此 Angular 应用程序永远不会出现。

最后的更改在我的index.html文件中(由浏览器加载的 HTML 文件,其中“包含”Angular 应用程序)。这是我更改之前的样子:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My App</title>
  <base href="/">
</head>
<body>
  <app-root>Loading...</app-root>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

<app-root>元素中的“正在加载...”文本在Angular 应用程序初始化时显示,并在引导过程完成后替换为应用程序内容。

确保“哎呀!” 如果应用程序无法初始化,则会显示消息,我添加了一个<style>带有一些 CSS 样式的块,以及<app-root>元素中的一些额外内容:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My App</title>
  <base href="/">

  <style type="text/css">
    #failure-message {
      display: none;
    }

    body.failed #loading-message {
      display: none;
    }

    body.failed #failure-message {
      display: block;
    }
  </style>

</head>
<body>
<app-root>
  <h1 id="loading-message">Loading...</h1>
  <h1 id="failure-message">Oops! Something went wrong.</h1>
</app-root>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

所以现在,默认显示“正在加载...”消息(如果初始化成功,将替换为应用程序内容),但是“哎呀!” 如果<body>元素具有failed类,则会显示消息- 这是我们在异常处理程序中添加的内容。

当然,您可以比简单的<h1>标签做得更好——您可以将任何您喜欢的内容放入其中。

所有这些的最终效果是浏览器显示“正在加载...”,然后要么加载 Angular 应用程序,要么将“正在加载...”文本替换为“哎呀!”。

请注意,URL 没有更改,因此如果您在浏览器中点击重新加载,它将从头开始并尝试重新加载 Angular 应用程序 - 这可能是您想要的。


小智 0

我会更新你的初始化程序捕获而不是抛出 toGlobalErrorHandler

   this.http.get(environment.serviceUrl + 'config/config')
        .catch((error: any) => {
            window.location.href = '/relativepath/different.html';
            return Observable.throw(error || 'Server error')
        })
        .subscribe((responseData) => {
            this.config = responseData;
            this.config['service_endpoint'] = environment.serviceUrl;
            resolve(true);
        });
Run Code Online (Sandbox Code Playgroud)

  • 此解决方案会导致应用程序循环重定向:app init-&gt;error(http)-&gt;redirect ---&gt; app init-&gt;error (http)-&gt;redirect ---&gt; app init-&gt;.... (2认同)