DEV Community

Cover image for Why I Stopped Writing tap() Inside rxResource Streams
Yasin Demir
Yasin Demir

Posted on • Originally published at ysndmr.com

Why I Stopped Writing tap() Inside rxResource Streams

There's a pattern I see a lot in Angular codebases that adopted Signals early: a developer discovers rxResource, loves that it handles loading and error state automatically, and then immediately reaches for tap() to write a signal inside the stream.

private readonly resource = rxResource({
    params: () => this.paramsSignal(),
    stream: ({ params }) =>
        this.api.fetch(params).pipe(
            tap(data => this.sideSignal.set(data.meta)) // 💥
        )
});
Enter fullscreen mode Exit fullscreen mode

This looks harmless. It runs in development without complaint in zone-based Angular. Then you enable zoneless — or Angular tightens its reactive graph enforcement — and you get NG0600: Writing to signals is not allowed in a reactive context.

The rxResource stream runs inside Angular's reactive scheduler. Signal writes there aren't just discouraged — they're illegal by design. The scheduler assumes computed signals and reactive contexts are read-only during evaluation. A write mid-computation breaks the glitch-free guarantee Angular's signal graph is built on.

The fix I landed on: make the stream return everything it needs to return, as a single typed value.

interface ResourceValue {
    readonly sections: Section[];
    readonly meta: Meta;
}

private readonly resource = rxResource<ResourceValue, Params>({
    stream: ({ params }) =>
        this.api.fetch(params).pipe(
            map(data => ({
                sections: transform(data),
                meta: data.meta
            }))
        )
});
Enter fullscreen mode Exit fullscreen mode

No tap. No side signal. Everything the rest of the store needs lives in resource.value() and can be read via computed.

The lesson isn't "don't use tap". The lesson is that rxResource has a contract: it is a read primitive. Its stream is for fetching and transforming. If you're writing signals inside it, you're treating it as a command bus — and that's a different tool.


Originally published on ysndmr.com.

Top comments (0)