Why I Stopped Writing tap() Inside rxResource Streams
Writing signals via tap() inside an Angular rxResource stream triggers NG0600. The fix: return one typed value from the stream instead of side-effecting.
A common anti-pattern has emerged in Angular codebases that adopted Signals early: developers use rxResource for its automatic loading and error handling, then reach for tap() to write to another signal from inside the stream. This works silently under zone-based Angular, but once zoneless mode is enabled or Angular tightens its reactive graph enforcement, it throws NG0600 — signal writes are illegal inside a reactive context.
The root cause is that the rxResource stream executes within Angular's reactive scheduler, which assumes computed signals and reactive contexts remain read-only during evaluation. A mid-computation write breaks the glitch-free guarantee underpinning Angular's signal graph.
The fix is to have the stream return everything it needs as a single typed value. Instead of using tap() for side effects, map() combines fields like sections and meta into one ResourceValue interface, which the rest of the code reads via resource.value() or computed.
The takeaway isn't to abandon tap() altogether, but to recognize that rxResource is a read primitive: its stream is meant for fetching and transforming data. Writing signals inside it effectively misuses it as a command bus — a job better suited to a different tool.