Skip to main content

cache.watchQuery

Watch data directly in the cache.

Unlike Apollo.watchQuery, it can only query the cache and does not execute a network request or any of Apollo Link's middleware.

It is ideal when querying local-only data.

API

Apollo.cache.watchQuery<TData, TVariables, TPartial>(
options: CacheQueryOptions<TData, TVariables, TPartial>
): CacheQueryObservable<TData, TVariables, TPartial>

Apollo.cache.watchQuery.required<TData, TVariables>(
options: Omit<CacheQueryOptions<TData, TVariables, false>, 'returnPartialData'>
): CacheQueryObservable<TData, TVariables, false, true>

Executing a cache query

import { AsyncPipe } from '@angular/common';
import { Apollo } from '@apollo-orbit/angular';
import { map } from 'rxjs';
import { THEME_QUERY } from '../graphql';

@Component({
selector: 'app-theme',
template: `
<span>Current theme:</span>
@if (theme$ | async; as theme) {
<span>{{ theme.displayName }}</span>
}
`,
imports: [
AsyncPipe
]
})
export class ThemeComponent {
private readonly apollo = inject(Apollo);

protected readonly theme$ = this.apollo.cache.watchQuery({ query: THEME_QUERY }).pipe(
map(({ data }) => data?.theme)
);
}

Reading the result

The result is a discriminated union. Narrowing on complete gives fully typed data. Cache reads return Unmasked<TData> when using Apollo Client's data masking.

this.apollo.cache.watchQuery({ query: THEME_QUERY }).subscribe(result => {
if (result.complete) {
// `result.data` is `ThemeQuery`
console.log(result.data.theme.displayName);
} else {
// `result.data` is `null`, and `result.missing` says which fields were absent
console.log(result.missing?.message);
}
});

Required cache queries

Where a type policy guarantees the data, narrowing guards a branch that can never be taken. required says so up front and each result carries fully typed data.

this.apollo.cache.watchQuery.required({ query: THEME_QUERY }).pipe(
map(({ data }) => data.theme)
);

Every emission is a CacheQueryCompleteResult<TData>. returnPartialData is not accepted, since asking for partial data while asserting completeness contradicts itself.

The assertion is checked rather than taken on trust. An incomplete read reaches the observable's error channel instead of emitting, and getCurrentResult() throws:

A required cache query read an incomplete result. Can't find field 'theme' on ROOT_QUERY object

The error closes the subscription and removes its cache watch. Subscribe again for later updates. signal.cacheQuery.required keeps watching and recovers when complete data arrives.

Use it where a type policy or a guaranteed write makes the data available. Otherwise, narrow on complete instead.

Partial data

Incomplete reads return data: null by default. With returnPartialData: true, they return DataValue.Partial<Unmasked<TData>> | null; complete results remain fully typed. A runtime boolean flag also permits partial data.

Use a TypedDocumentNode for inference, or supply the partial-data flag explicitly: cache.watchQuery<TData, TVariables, true>({ query, returnPartialData: true }).

Comparison

Apollo.cache.watchQuery has few pros and cons compared to Apollo.watchQuery method.

Pros

  • Each result is a union that narrows on complete, so a complete read reaches fully typed data with no optional fields.
  • There's no need to handle loading and error states.
  • Synchronous observable execution
    • The data is returned instantly when the observable is subscribed to.
    • When the observable is subscribed to from an Angular component template, the template will complete rendering in a single cycle.
    • View children referenced in the component will be available in ngAfterViewInit lifecycle hook.

Cons

  • Does not execute a network request if the data is not available in the cache.
  • data is null unless every requested field is available in the cache, so it needs narrowing or a null check.
    • Set returnPartialData to true to receive the fields the cache did have instead.

Cyclic cache updates

Because of the synchronous nature of Apollo.cache.watchQuery, attempting to update the cache in the observable's subscribe callback will cause Apollo Client to throw an already computing error.
This can be avoided by piping the observable through observeOn(asyncScheduler) which will queue the observer.next call after cache update operation is complete, mimicking the behaviour of Apollo.watchQuery.

this.apollo.cache.watchQuery({ query: THEME_QUERY }).pipe(
observeOn(asyncScheduler),
).subscribe(({ data }) => {
// Update cache
});