In RxJS an error is a terminal signal: the Observable fails, no more values. Sometimes that's inconvenient — we want to show an "error card" in the UI and keep accepting new triggers. Turn the error into an ordinary next via materialize/dematerialize.
The problem it solves
A plain catchError(() => of('error')) inside the inner stream handles 95% of cases. materialize is for special cases where working with notification objects matters (testing, logging all event types).
Operators and why they matter
materialize() — turns next/error/complete into {kind, value} objects. A stream of notifications.
map — replace the error-notification with an ordinary next carrying an error-state.
dematerialize() — returns the stream to ordinary next/complete semantics.
Gotchas
Leave the error-notification as-is and run it through dematerialize and the stream still fails.
Don't use materialize where a plain catchError is more readable. It's a tool for rare cases.
Error-as-data requires explicit handling in the UI. Otherwise the error looks like an ordinary value and the user won't understand what happened.
What you get
The error becomes part of the data, and the stream keeps living. The subscribe error handler is NOT called.