fromQuery
projects/interop/query/from-query.ts
Description#
Bridge that projects a TanStack Query result onto CngxAsyncState<T>.
Reads the query's signal-bag and maps TanStack's status / fetchStatus
pair onto the cngx AsyncStatus union, then hands the derived signals to
buildAsyncStateView - the same single-source-of-truth kernel every other
producer uses. No injection context is required (only computed()), and no
boolean view is re-derived here.
Status mapping:
error+fetching->loading(no retained data) /refreshing(data retained) - a retry out of error reports busy againerror+ idle/paused ->errorsuccess+fetching->refreshing(background refetch, data visible)success+ idle/paused ->successpending+fetching->loading(first load, no data yet)pending+ idle/paused ->idle(disabled or paused query)
With placeholderData, TanStack reports success + fetching while the
first real load runs - that maps to refreshing over the placeholder,
deliberately suppressing the skeleton (the intended TanStack UX).
isFirstLoad is !hadSuccess, the kernel-recommended query semantics: a
failed or retrying first load stays isFirstLoad === true until data
actually arrived once. Bind dataUpdatedAt for the exact latch; without it
the bridge falls back to status === 'success' or retained data. Two
deliberate divergences from fromResource: a settled first-load error
stays first-load here (fromResource flips false on error), and the latch
is derived, so a query reset / key swap (dataUpdatedAt back to 0)
returns to first-load instead of staying latched forever.
private readonly query = injectQuery(() => ({
queryKey: ['users', this.filter()],
queryFn: () => fetchUsers(this.filter()),
}));
readonly users = fromQuery(this.query);
// users.status(), users.data(), users.isFirstLoad() - all work
// <cngx-async-container [state]="users"> - direct binding