The user types a username or email, and the server answers: "available" or "already taken." This is async validation — a check that can't pass locally; it needs the server.
The problem it solves
Every keystroke is a potential HTTP request. Without protection you get dozens of requests, and a slow response for alice may arrive AFTER the response for taken and overwrite the field's current state. The form shows "available" when it's really "taken".
Operators and why they matter
debounceTime — wait for a pause so we don't hit the server on every letter.
distinctUntilChanged — if the value hasn't changed, don't re-check.
switchMap — a new request cancels the old one. This is the key to guarding against the response race.
catchError inside switchMap — an API error becomes a validation state instead of killing the whole valueChanges stream.
map — normalize the server response into an object the form understands.
Gotchas
mergeMap instead of switchMap — an old slow response overwrites a new one. The race comes back.
exhaustMap is harmful here: while an old check is in flight, new values are ignored — you'll see the status for input that's already been cleared.
catchError outside the whole pipeline — the first error ends the entire validation stream, and validation stops working forever.
What you get
The user sees only the result for the last stable value. Network failures are just another field state, not a broken form.