The Submit button depends on value, status, dirty, and server availability. The error list depends on value and status. The preview depends on value. Instead of keeping four async pipes in the template, we assemble one VM object that holds everything needed.
The problem it solves
When related data comes from different streams, the template can momentarily render an inconsistent state: canSubmit=true, say, but errors=[required]. Imperative component fields give the same effect — five handlers, each assigning its own thing, and the execution order decides whether the UI is correct.
Operators and why they matter
combineLatest — rebuilds the VM whenever any source changes. This is the heart of the pattern.
startWith — gives initial values so combineLatest emits right away instead of staying silent until each field's first change.
map — a pure projection: from four inputs it computes {canSubmit, errors, preview}.
shareReplay({ bufferSize: 1, refCount: true }) — caches the latest VM. A subscriber arriving later gets the current state immediately.
Gotchas
shareReplay(1) without a config holds the upstream forever. Use the object config with refCount: true.
Don't compute canSubmit in several places (once for the button, once for the hint) — the rules will drift apart eventually. One source of truth: vm.canSubmit.
Without startWith, combineLatest stays silent until EVERY source emits. The template will be empty until the first interaction.
What you get
The template gets one consistent object that fully describes the form's state. No intermediate "half-valid" renders.