Angular 애플리케이션을 실행하는 동안 코드에서 오류가 발생할 수 있습니다. 이 오류를 처리하지 않으면 예상치 못한 동작이 발생하거나 UI가 응답하지 않을 수 있습니다. 이 가이드는 애플리케이션 코드에서 명시적으로 잡지 않은 오류를 Angular가 어떻게 처리하는지 설명합니다. 애플리케이션 안에 직접 오류 처리 로직을 작성하는 방법은 JavaScript와 Angular의 오류 처리 모범 사례를 참고하세요.
Angular 오류 처리 전략의 기본 원칙은 가능한 한 오류가 발생한 호출 지점에서 개발자에게 오류를 드러내는 것입니다. 그러면 작업을 시작한 코드가 오류를 이해하고 적절히 처리하며, 애플리케이션을 어떤 상태로 둘지 결정하는 데 필요한 맥락 정보를 활용할 수 있습니다. 오류를 발생 지점에서 드러내면 개발자는 실패한 작업에 맞는 오류 처리를 구현하고, 복구하거나 최종 사용자에게 유용한 피드백을 제공하는 데 필요한 정보에 접근할 수 있습니다. 이는 원인을 이해하기에 충분한 맥락 없이 오류를 보고하는 "지나치게 일반적인 오류"라는 문제도 피하는 데 도움이 됩니다.
예를 들어 API에서 사용자 데이터를 가져오는 컴포넌트를 생각해 봅시다.
API를 호출하는 코드에는 잠재적인 네트워크 문제나 API가 반환하는 오류에 대응할 오류 처리 로직이 포함되어야 합니다(예: try...catch 블록이나 RxJS의 catchError 연산자).
그러면 오류를 처리하지 않은 채 전파시키는 대신 사용자에게 이해하기 쉬운 오류 메시지를 표시하거나 요청을 재시도할 수 있습니다.
처리되지 않은 오류는 ErrorHandler에 전달됩니다
Angular는 처리되지 않은 오류를 애플리케이션 루트의 ErrorHandler에 전달합니다.
커스텀 ErrorHandler를 제공하려면 bootstrapApplication을 호출할 때 ApplicationConfig에 등록하세요.
Angular 애플리케이션을 만들 때는 프레임워크가 자동으로 호출하는 코드를 작성하는 경우가 많습니다.
예를 들어 컴포넌트가 템플릿에 나타나면 Angular가 컴포넌트의 생성자와 라이프사이클 메서드를 호출합니다.
프레임워크가 개발자의 코드를 실행하는 경우에는 오류를 적절히 처리할 try 블록을 추가할 마땅한 위치가 없습니다.
이런 상황에서 Angular는 오류를 잡아 ErrorHandler에 전달합니다.
Angular는 작성한 코드가 직접 호출하는 API 내부의 오류를 잡지 않습니다.
예를 들어 서비스에 오류를 던지는 메서드가 있고 컴포넌트에서 그 메서드를 호출하더라도 Angular가 해당 오류를 자동으로 잡지는 않습니다.
try...catch 등의 방법으로 직접 처리해야 합니다.
Angular는 사용자가 작성한 프로미스나 옵저버블의 비동기 오류를 다음 조건에서만 잡습니다:
- Angular가 비동기 작업을 기다렸다가 결과를 사용한다는 명시적인 규약이 있고,
- 오류가 반환값이나 상태로 표현되지 않는 경우입니다.
예를 들어 AsyncPipe와 PendingTasks.run은 오류를 ErrorHandler에 전달하지만, resource는 status와 error 프로퍼티로 오류를 나타냅니다.
Angular가 ErrorHandler에 전달하는 오류는 예상치 못한 오류입니다.
복구할 수 없는 오류이거나 애플리케이션의 상태가 손상되었다는 신호일 수 있습니다.
ErrorHandler에 의존하기보다는 가능한 한 오류가 발생하는 곳에서 try 블록이나 적절한 오류 처리 연산자(RxJS의 catchError 등)로 오류를 처리해야 합니다.
ErrorHandler는 치명적일 수 있는 오류를 오류 추적 및 로깅 시스템에 보고하는 용도로만 사용하는 것이 일반적이며 적절합니다.
export class GlobalErrorHandler implements ErrorHandler {
private readonly analyticsService = inject(AnalyticsService);
private readonly router = inject(Router);
handleError(error: any) {
const url = this.router.url;
const errorMessage = error?.message ?? 'unknown';
this.analyticsService.trackEvent({
eventName: 'exception',
description: `Screen: ${url} | ${errorMessage}`,
});
console.error(GlobalErrorHandler.name, {error});
}
}
TestBed는 기본적으로 오류를 다시 던집니다
많은 경우 ErrorHandler는 오류를 기록하기만 하고 애플리케이션은 계속 실행하도록 할 수 있습니다.
하지만 테스트에서는 거의 항상 이러한 오류를 드러내야 합니다.
Angular의 TestBed는 프레임워크가 잡은 오류를 실수로 놓치거나 무시하지 않도록 예상치 못한 오류를 다시 던집니다.
드물게는 오류가 발생해도 애플리케이션이 응답하지 않거나 비정상 종료되는 일이 없는지 확인하는 테스트가 필요할 수 있습니다.
이 경우 TestBed.configureTestingModule({rethrowApplicationErrors: false})로 TestBed가 애플리케이션 오류를 다시 던지지 않도록 설정할 수 있습니다.
앱이 시작되는 동안 발생하는 오류
Angular가 아직 ErrorHandler에 오류를 전달할 수 없는 짧은 시점이 있습니다. 앱의 최상위 모듈이나 컴포넌트를 생성하는 동안입니다.
등록한 ErrorHandler를 찾으려면 최상위 인스턴스가 먼저 존재해야 하므로, 그 전에 발생한 오류는 ErrorHandler로 전달되지 않고 일반적인 처리되지 않은 오류처럼 동작합니다.
이 문제는 Angular 엘리먼트를 사용할 때 발생하기 쉽습니다. 페이지에 태그가 이미 존재하는 상태에서 커스텀 엘리먼트를 정의하면 브라우저는 즉시 이 엘리먼트를 업그레이드합니다. 이때 초기화 과정에서 컴포넌트 생성자와 ngOnInit이 실행되므로 여기서 발생한 오류를 놓칠 수 있습니다.
이런 문제가 발생하면 오류를 발생시키는 코드가 앱 초기화 이후에 실행되도록 옮기세요. 다음과 같은 방법을 사용할 수 있습니다.
setTimeout(() => /* 실행할 코드 */)으로 감싸 초기화 중이 아닌 별도의 실행 차례에 실행합니다.- 생성자에서 실행하는 대신
APP_BOOTSTRAP_LISTENER로 등록합니다. - 최상위 모듈의 생성자 대신
ngDoBootstrap에서 커스텀 엘리먼트를 정의합니다.
이 오류는 브라우저에 처리되지 않은 오류로 전달되므로, 아래에서 설명하는 provideBrowserGlobalErrorListeners()를 활성화하는 것도 도움이 됩니다.
전역 오류 리스너
애플리케이션 코드와 프레임워크의 애플리케이션 인스턴스가 모두 잡지 못한 오류는 전역 스코프에 도달할 수 있습니다. 전역 스코프에 도달한 오류를 고려하지 않으면 의도하지 않은 결과가 발생할 수 있습니다. 브라우저가 아닌 환경에서는 프로세스가 비정상 종료될 수 있습니다. 브라우저에서는 오류가 보고되지 않은 채 남고 사이트 방문자가 브라우저 콘솔에서 오류를 볼 수도 있습니다. Angular는 이러한 문제에 대응하도록 두 환경 모두에 전역 리스너를 제공합니다.
클라이언트 사이드 렌더링
ApplicationConfig에 provideBrowserGlobalErrorListeners()를 추가하면 브라우저 창에 'error'와 'unhandledrejection' 리스너를 등록하고 오류를 ErrorHandler에 전달합니다.
Angular CLI는 새 애플리케이션을 생성할 때 이 프로바이더를 기본적으로 포함합니다.
Angular 팀은 대부분의 애플리케이션에서 프레임워크의 내장 리스너나 커스텀 리스너로 이러한 전역 오류를 처리할 것을 권장합니다.
커스텀 리스너를 제공한다면 provideBrowserGlobalErrorListeners를 제거할 수 있습니다.
서버 사이드 렌더링과 하이브리드 렌더링
Angular에서 SSR을 사용하면 Angular는 서버 프로세스에 'unhandledRejection'과 'uncaughtException' 리스너를 자동으로 등록합니다.
이 핸들러는 서버가 비정상 종료되는 것을 막고 잡은 오류를 콘솔에 기록합니다.
중요: 애플리케이션이 Zone.js를 사용한다면 'unhandledRejection' 핸들러만 추가합니다.
Zone.js가 있으면 애플리케이션의 존 내부에서 발생한 오류는 이미 애플리케이션의 ErrorHandler로 전달되므로 서버 프로세스까지 도달하지 않습니다.