Skip to main content

signal.query.once

Fetch data once per execution using reactive Signals.

Unlike Apollo.signal.query, it stops listening once the data is fetched: cache writes, refetches and subscriptions elsewhere in the application never re-emit into its signals. It is the Signal equivalent of Apollo.query, whereas signal.query is the equivalent of Apollo.watchQuery.

It is ideal when hydrating a form.

API

Apollo.signal.query.once<TData, TVariables>(
options: SignalSingleQueryOptions<TVariables, TData>
): SignalSingleQuery<TData, TVariables>

Returns a SignalSingleQuery<TData, TVariables> instance. This object provides the same reactive Signals as SignalQuery (result, data, loading, error, networkStatus, previousData), without the methods that depend on watching the cache.

Options
PropertyTypeDescription
queryDocumentNode | TypedDocumentNode<TData, TVariables>A GraphQL query string parsed into an AST with the gql template literal.
errorPolicy?ErrorPolicySpecifies how the query handles a response that returns both GraphQL errors and partial results.

For details, see GraphQL error policies.

The default value is none, meaning that the query result includes error details but not partial results.
context?DefaultContextIf you're using Apollo Link, this object is the initial value of the context object that's passed along your link chain.
fetchPolicy?FetchPolicySpecifies how the query interacts with the Apollo Client cache during execution (for example, whether it checks the cache for results before sending a request to the server).

For details, see Setting a fetch policy.

The default value is cache-first.
notifyOnLoading?booleanWhether or not to track initial network loading status.
@default: true
lazy?booleanWhether to execute query immediately or lazily via execute method.
injector?InjectorCustom injector to use for this query.
variables?() => TVariables | undefined | A function or signal returning an object containing all of the GraphQL variables your operation requires to execute.

Each key in the object corresponds to a variable name, and that key's value corresponds to the variable value.

When null is returned, the operation will be terminated until a non-null value is returned again.
Signals
SignalTypeDescription
resultSignal<QueryResult<TData, complete | empty>>The query result, containing data, loading, error, networkStatus, previousData, dataState.
loadingSignal<boolean>If true, the query is currently in flight.
networkStatusSignal<NetworkStatus>The current network status of the query.
dataSignal<TData | undefined>The data returned by the query, or undefined if loading, errored, or no data received yet.
previousDataSignal<TData | undefined>The data from the previous execution, useful for displaying stale data while re-executing.
errorSignal<ErrorLike | undefined>An error object if the query failed, undefined otherwise.
activeSignal<boolean>Whether the query is currently active, having executed and not been terminated since.
enabledSignal<boolean>Whether the query is currently enabled.

This property starts as true for non-lazy queries and false for lazy queries.

Calling execute() sets it to true, while calling terminate() sets it to false.

When true:
- The query automatically executes when variables change from null to a non-null value
- Variable changes trigger re-execution with the new variables

When false:
- Variable changes are ignored and do not trigger re-execution
- The query must be manually started via execute()

Note: This is different from active, which indicates whether the query has executed and not been terminated since.
Methods
MethodDescription
execute(execOptions: SignalQueryExecOptions<TVariables>)Execute the query with the provided options.
terminate()Terminate the query, cancelling any in-flight execution and ignoring further variable changes.

Hydrating a form

A form is edit state, not view state. Once the user starts typing, the server is no longer the source of truth for what's on screen, so the query that seeded the form must stop emitting.

Fetch the book with signal.query.once and derive the form value from it with linkedSignal — no effect, no write:

library/books/edit-book.component.ts
import { linkedSignal } from '@angular/core';
import { form, required } from '@angular/forms/signals';
import { Apollo } from '@apollo-orbit/angular';
import { BOOK_QUERY } from '../graphql/types';

@Component({
selector: 'app-edit-book',
templateUrl: './edit-book.component.html'
})
export class EditBookComponent {
private readonly apollo = inject(Apollo);

public readonly bookId = input.required<string>();

protected readonly bookQuery = this.apollo.signal.query.once({
query: BOOK_QUERY,
variables: () => ({ id: this.bookId() })
});

protected readonly value = linkedSignal({
source: this.bookQuery.data,
computation: data => ({ name: data?.book.name ?? '', genre: data?.book.genre ?? null })
});

protected readonly form = form(this.value, schema => {
required(schema.name);
});
}
note

linkedSignal rebuilds value whenever its source changes. Had this been signal.query, every cache write to that book — an updateBook mutation from another component, a refetchQueries, an incoming subscription — would re-emit and silently discard whatever the user had typed.

signal.query.once emits once per execution, so value is only rebuilt when bookId changes.

The fetched book is still written to the cache, so any watching query picks it up. The relationship is one-directional: SignalSingleQuery publishes to the cache but does not subscribe to it.

tip

Reach for signal.query.once whenever the data must not shift under the user: a report or export being read, a point-in-time snapshot, or a lookup whose result is consumed once.

Variables

Variables behave exactly as they do in signal.query. Passing a function or signal re-executes the query whenever its reactive dependencies change, and previousData holds the last value while the new execution is in flight.

Variables = null

When the variables function returns null, the query is terminated and data is reset to undefined, preserving the last value in previousData. Returning a non-null value again re-executes the query, provided it is enabled.

Lazy Queries

Setting lazy: true defers the first execution until execute() is called, and makes the query variables optional (even if required by the query):

library/books/books.component.ts
export class BooksComponent {
private readonly apollo = inject(Apollo);

protected readonly booksQuery = this.apollo.signal.query.once({
query: BOOKS_QUERY,
lazy: true
});

protected async onExportClicked(): Promise<void> {
const { error, data } = await this.booksQuery.execute({ variables: { genre: 'Fiction' } });

if (error) {
// Optionally handle error
} else if (data) {
// Optionally handle data
}
}
}
info

The promise returned by execute always resolves without a rejection even if the query encounters errors, removing the need for try...catch or dealing with unhandled promise rejections.