programing

APP_INITIALIZER에서 복구할 수 없는 예외를 처리하고 사용자에게 알리는 방법은 무엇입니까?

css3 2023. 6. 14. 22:06

APP_INITIALIZER에서 복구할 수 없는 예외를 처리하고 사용자에게 알리는 방법은 무엇입니까?

애플리케이션 초기화 중에 Angular5 앱이 백엔드에서 구성 파일을 로드합니다(APP_INITIAZER).앱이 없으면 앱을 실행할 수 없기 때문에, 제 목표는 사용자에게 구성을 로드할 수 없다는 메시지를 보여주는 것입니다.

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

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);
                });
        });
    }
}

글로벌 예외 처리기:

@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;
    }

}

구성 파일을 로드할 수 없는 경우 글로벌 예외 핸들러에서 예외가 발생하거나 발견되거나 다시 던져집니다(console.log()의 HTTPErrorResponse가 발견되지 않았으며 로드 스피너가 영원히 중단됨).

AppComponent할 수 없기). " Component는 의 하위 입니다. 그리고 내 메시지/"으르렁" 구성 요소는 다음의 하위 구성 요소입니다.AppComponent사용자에게 메시지를 표시할 수 없습니다.

메시지를 표시할 수 있는 방법이 있습니까?index.html이 단계에서 페이지?html.index. 파일의 .html 의 .html 파일을 다시 .. 그러면 사용자가 error.html에서 f5를 다시 로드하기만 하기 때문입니다.

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;
});

발생한 예외가 발견되고 메시지가 html 요소로 설정됩니다.요소는 app-root 태그의 index.html에 정의되어 있습니다.

<app-root><div id="errorMsgElement" style="padding: 20% 0; text-align: center;"></div> 
</app-root>

부트스트랩이 성공한 경우 태그 내용이 각도로 바뀝니다.

유사한 문제에 대한 좋은 해결책도 찾지 못했지만, 해결책으로 다음과 같은 방법을 했습니다.

  • 를 확장할 때는 " " " " 입니다." 를 합니다.initialized변수:

    @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);
                    });
            });
        }
    }
    
  • 리고그 안서에 에.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>
    
  • 글로벌 예외 처리기:

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

물론 구체적인 오류 메시지/예외 텍스트를 구성 개체에 저장하고 필요한 경우 app.component/error.page.component에 있는 사용자에게 보여줄 수도 있습니다.

저는 제안하는 방법이 조금 다릅니다.제 생각에는 Angular app의 부트스트랩이 제대로 작동하지 않을 것이기 때문에 초기화를 제대로 할 수 없다면 부트스트랩이 계속되는 것을 원치 않습니다.대신 사용자에게 메시지를 표시하고 중지합니다.

배경: 초기화 코드에서 특정 순서로 두 개의 비동기 요청을 해야 합니다.

  1. 나는 내 백엔드 API를 호스팅하는 두 번째 웹 서버의 호스트 이름을 얻기 위해 내 "자체" 웹 서버(Angular 앱이 제공되는 곳)에 HTTP 요청을 합니다.

  2. 그런 다음 두 번째 웹 서버에 추가 구성 정보를 검색하도록 요청합니다.

이러한 요청 중 하나가 실패하면 부트스트랩 프로세스를 계속 진행하는 Angular가 아닌 중지하고 메시지를 표시해야 합니다.

다음은 새 예외 처리에 추가하기 전에 코드를 단순화한 버전입니다(사용하기를 선호하는 참고).Promise그리고.async/awaitObservable하지만 그것은 실제로 관련이 없습니다):

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
};

이 코드는 오류를 콘솔에 기록한 다음 부트스트랩 프로세스를 계속합니다. 이 프로세스는 우리가 원하는 것이 아닙니다.

를 (, 저는 그 세 했습니다.console.error 전화 번호:

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

      const forever = new Promise(() => { }); // will never resolve
      await forever;

번째 하면 됩니다.<body>후에 그것이 어떤 영향을 미치는지 잠시 후에 알아보겠습니다.

두 줄은 나지두줄은머▁the줄은두▁other▁lines.await a Promise그것은 결코 해결되지 않습니다 - 다시 말해서, 그들은 영원히 기다립니다.이렇게 하면 Angular 부트스트랩 프로세스가 지연되어 Angular 앱이 나타나지 않습니다.

마지막 변화는 나의 것입니다.index.html파일(브라우저에서 로드하고 Angular 앱을 "포함"하는 HTML 파일).변경하기 전의 모습은 다음과 같습니다.

<!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>

그 "로드 중...내의 텍스트<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>

로, " 이딩중자filename제..." 됨), "!" 는 대신 메시지가 표시됩니다.<body>요소는 다음과 같습니다.failed클래스 - 예외 처리기에 추가하는 항목입니다.

물론, 당신은 단순한 것보다 더 잘 할 수 있습니다.<h1>태그 - 원하는 내용을 넣을 수 있습니다.

이 모든 것의 실질적인 효과는 브라우저에 "loading..."이 표시된다는 것입니다.Angular app 로드 또는 "loading..." 중 하나를 선택합니다.텍스트가 "context!"로 대체됩니다.

URL은 변경되지 않으므로 브라우저에서 다시 로드를 누르면 처음부터 다시 시작하여 Angular 앱을 다시 로드하려고 합니다. 이것이 아마도 당신이 원하는 것일 것입니다.

GlobalErrorHandler에 던지기보다는 당신의 이니셜라이저 캐치를 업데이트하겠습니다.

   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);
        });

제가 하는 방법은 이렇습니다.

// app.module.ts

export function loadAppsettings(http: HttpClient) {
  return async () => {
    try {
      const as = await http
          .get<IAppsettings>(`//${window.location.host}/appsettings.json`)
          .toPromise();
      // `appsettings` is a global constant of type IAppsettings
      Object.assign(appsettings, as); 
      return as;
    }
    catch (e) {
      alert('Exception message...');
      throw e;
    }
  };
}

@NgModule({
  // ...
  providers: [
    // ...
    { 
      provide: APP_INITIALIZER, 
      useFactory: loadAppsettings, 
      multi: true, 
      deps: [HttpClient]
    },
  ]
})
export class AppModule { ... }

조금이나마 도움이 되길 바랍니다 :-)

따라서 리디렉션하려는 경우 src 폴더 내에 error.html 파일을 생성할 수 있습니다.그런 다음 오류가 발생한 후 해당 파일로 사용자를 리디렉션합니다.

error.html을 생성한 후 angular.json 파일에서 각 애플리케이션을 로드하지 않고 리디렉션하지 않도록 자산에 추가합니다.

 "assets": [
       {
        "glob": "**/*",
        "input": "src/assets",
        "output": "/assets"
       },
       {
         "glob": "error.html",
         "input": "src",
         "output": "/"
       }
    ],

그런 다음 로드 함수를 다음으로 변경합니다.

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

제가 사용한 해결책은 인터셉트와 오류 서비스를 만드는 것이었습니다.인터셉터는 내가 오류라고 알고 있는 특정 상태 코드로 모든 HTTP 오류를 탐지합니다.

인터셉트카 예제

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Router } from '@angular/router';

import { ErrorService } from '@app/@pages/error/error.service';

import { catchError } from 'rxjs/operators';
import { Observable, throwError } from 'rxjs';

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {

    constructor(
        private router: Router,
        private errorSvc: ErrorService
    ) { }

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        return next
            .handle(req)
            .pipe(
                catchError((error: any, resp: Observable<HttpEvent<any>>) => {
                    if (error instanceof HttpErrorResponse && (error.status === 0 || error.status > 400)) {
                        this.errorSvc.setHttpError(error);
                        this.router.navigate(['/error']);
                    }
                    return throwError(error);
                })
            );
    }
}

오류 서비스 예제

import { Injectable } from '@angular/core';

import { LocalStorageService } from 'angular-2-local-storage';

import { BehaviorSubject, Observable } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';

@Injectable({
    providedIn: 'root'
})
export class ErrorService {

    private error$: BehaviorSubject<HttpErrorResponse> = new BehaviorSubject(this.localStorageSvc.get<HttpErrorResponse>('error'));

    constructor(private localStorageSvc: LocalStorageService) { }

    setHttpError(error: HttpErrorResponse): void {
        this.localStorageSvc.set('error', error);
        this.error$.next(error);
    }

    getHttpError(): Observable<HttpErrorResponse> {
        return this.error$.asObservable();
    }
}

따라서 APP_INITIAZER를 통해 구성 데이터를 로드하려고 할 때 인터셉터가 오류를 감지합니다.오류가 발견되면 응용 프로그램을 오류 페이지로 라우팅해야 합니다.오류 서비스는 로컬 스토리지와 rxjs 동작 주체를 사용하여 오류 상태를 유지합니다.오류 구성 요소는 이 서비스를 생성자에 주입하고 구독하여 오류 세부 정보를 표시합니다.

언급URL : https://stackoverflow.com/questions/48564687/how-to-handle-inform-users-about-unrecoverable-exceptions-in-an-app-initializer