Observable 기반 리소스인 rxResource 또는 httpResource가 값이나 오류를 한 번도 전달하지 않은 채 완료되면 이 오류가 발생합니다.
리소스는 항상 무언가 로 끝나야 합니다. 불러온 값이거나 실패 원인을 설명하는 오류가 있어야 합니다. 조용히 완료되기만 하는 옵저버블은 둘 중 어느 것도 제공하지 않으므로 Angular는 리소스에 무엇을 넣어야 할지 알 수 없어 이 오류를 발생시킵니다.
httpResource를 사용하는 경우
httpResource는 직접 작성할 옵저버블을 제공하지 않습니다. 내부적으로 HttpClient를 통해 HTTP 요청을 보내고 응답이 돌아오면 완료합니다. 따라서 httpResource에서 이 오류가 발생한다면 요청 자체보다 응답이 도착하기 전에 중간에서 응답을 삼키는 처리를 확인해야 합니다.
흔한 원인은 오류를 잡은 뒤 아무것도 다시 방출하지 않는 HttpInterceptor입니다. 아래의 EMPTY 실수와 같지만 직접 작성한 코드 대신 인터셉터 안에서 발생합니다.
@Injectable()
class MyInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<unknown>, next: HttpHandler) {
return next.handle(req).pipe(
// BAD: same problem as `catchError(() => EMPTY)` in a `stream` —
// the request completes with no response and no error, and any
// httpResource depending on it hits NG0991.
catchError(() => EMPTY),
);
}
}
일반적인 인터셉터 파이프라인과 같은 방식으로 수정하세요. catchError를 제거하여 오류가 전파되게 하거나, of(new HttpResponse({body: fallbackValue}))처럼 실제로 요청을 해결하는 값으로 대체하세요.
rxResource를 사용하는 경우
가장 흔한 실수는 오류를 잡은 뒤 EMPTY로 삼키는 것입니다.
import {rxResource} from '@angular/core/rxjs-interop';
import {EMPTY, catchError} from 'rxjs';
const userResource = rxResource({
params: () => ({id: userId()}),
stream: ({params}) =>
this.http.get(`/users/${params.id}`).pipe(
// BAD: this hides the error completely. The Observable just
// completes with nothing, and the resource has no value and no
// error to show for it — this is what triggers NG0991.
catchError(() => EMPTY),
),
});
여러 옵저버블을 merge, race 등으로 결합하면서 startWith(...)로 초깃값을 보장하지 않아 일부 옵저버블이 값을 방출하기 전에 완료되는 경우에도 발생할 수 있습니다. 결합한 모든 소스가 완료될 때까지 어느 소스도 값을 방출하지 않으면 결합된 스트림도 빈 상태로 완료됩니다.
상황에 맞는 해결 방법을 선택하세요.
오류를 전파하세요. 직접 오류를 처리할 필요가 없다면 catchError를 제거하세요. 리소스는 error 상태가 되며 userResource.error()로 확인할 수 있습니다.
stream: ({params}) => this.http.get(`/users/${params.id}`),
대체 값으로 복구하세요. 오류 대신 무언가를 표시하려면 catchError에서 of(...)처럼 값을 방출하는 옵저버블을 반환하세요.
import {of, catchError} from 'rxjs';
stream: ({params}) =>
this.http.get(`/users/${params.id}`).pipe(
catchError(() => of(null)), // resource resolves with `null` instead of erroring
),
어느 방법을 선택하든 stream에서 반환하는 옵저버블이 완료되기 전에 반드시 하나 이상의 값이나 오류를 방출하도록 하세요.
실제로 오류가 나타나는 위치
이 오류는 리소스를 생성할 때 발생하지 않으며 요청이 완료되는 위치에서 발생하는 것도 아닐 수 있습니다. 템플릿 바인딩의 userResource.value()처럼 다음에 리소스 값을 읽는 시점에 발생합니다. 따라서 실제 원인이 앞선 처리 과정의 빈 stream 완료나 응답을 삼키는 인터셉터에 있어도 애플리케이션의 다른 부분에 문제가 있는 것처럼 보일 수 있습니다.
코드에서 .value()를 읽기 전에 리소스 상태를 확인하지 않으면 템플릿 렌더링 중 발생한 다른 미처리 오류처럼 Angular의 전역 ErrorHandler까지 오류가 전파될 수 있습니다.
영향을 줄이는 방법은 권장하는 순서대로 다음 두 가지입니다.
- 근본 원인을 해결하세요. 위 방법 중 하나를 사용하여 리소스가 이러한 상태에 도달하지 않도록 하세요.
.value()읽기를 보호하세요. 완전히 통제할 수 없는 서드 파티 인터셉터나 서비스가 예기치 않게 빈 상태로 완료할 경우에 대비하는 추가 방어책입니다..value()를 무조건 읽지 말고 먼저.hasValue()또는.status()를 확인하세요. 예를 들어@if (userResource.hasValue()) { {{ userResource.value() }} }를 사용할 수 있습니다.