Error Encyclopedia

Resource completed before producing a value

This error happens when an Observable-backed resource — rxResource or httpResource — finishes (completes) without ever sending a value or an error.

A resource always needs to end up with something: a value it loaded, or an error explaining what went wrong. An Observable that just completes silently doesn't give Angular either of those, so Angular doesn't know what to put in the resource — and it throws this error instead.

With httpResource

httpResource doesn't give you an Observable to write yourself — it makes the HTTP call internally through HttpClient and completes once the response comes back. So if you're seeing this error from httpResource, the request itself isn't the problem; something sitting in front of it is swallowing the response before it gets there.

The usual suspect is an HttpInterceptor that catches an error and doesn't re-emit anything, the same EMPTY mistake as below but living in an interceptor instead of your own code:

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

Fix it the same way as you would in an interceptor pipeline generally: let the error propagate (drop the catchError), or replace it with something that actually resolves the request, like of(new HttpResponse({body: fallbackValue})).

With rxResource

The most common way to accidentally cause this is catching an error and swallowing it with EMPTY:

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

Another common way this happens: combining several Observables (with merge, race, and similar) where one of them can complete on its own before ever emitting, without a startWith(...) to guarantee an initial value. If none of the combined sources ever emits before they've all completed, the combined stream completes empty too.

Pick whichever fix makes sense for your case:

Let the error through. If you don't need to handle the error yourself, just remove the catchError — the resource will end up in its error state, which you can already check with userResource.error().

stream: ({params}) => this.http.get(`/users/${params.id}`),

Recover with a fallback value. If you'd rather show something instead of an error, use catchError to return an Observable that emits a value, such as 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
  ),

Either way, make sure the Observable you return from stream always emits at least one value or an error before it completes.

Where you'll actually see this error

The error isn't thrown when the resource is created, and not necessarily where the request completes either. It's thrown the next time something reads the resource's value — for example userResource.value() in a template binding. That can make it look like the bug is somewhere else in your app, when the real cause is upstream (the stream completing empty, or an interceptor swallowing the response).

If nothing in your code checks the resource's status before reading .value(), this error can bubble all the way up to Angular's global ErrorHandler, the same as any other uncaught error thrown while rendering a template.

Two ways to reduce the impact of this, from most to least preferred:

  1. Fix it at the root, using one of the approaches above, so the resource never ends up in this state.
  2. Guard reads of .value() as defense in depth, in case something you don't fully control (a third-party interceptor or service, for example) ever completes empty unexpectedly. Check .hasValue() or .status() before reading .value(), for example with @if (userResource.hasValue()) { {{ userResource.value() }} }, instead of reading .value() unconditionally.