Direct Cache Access
Apollo Client normalizes all of your data so that if any data you previously fetched from your GraphQL server is updated in a later data fetch from your server then your data will be updated with the latest truth from your server.
This normalization process is constantly happening behind the scenes when you call watchQuery
but
this process is often not enough to describe the updates to your data model as the result of a
mutation. For example, if you wanted to add an item to the end of an array fetched by one of your
queries. You also might want to read data from the normalized Apollo Client store at a specific id
without making another GraphQL server fetch.
To interact directly with your data in the Apollo Client store you may use the methods readQuery
,
readFragment
, writeQuery
, and writeFragment
that are accessible from the ApolloClient
class.
This article will teach you how to use these methods to control your data.
If you would like a better understanding of the data normalization process then we recommend reading the 'How it works' documentation article. Knowledge around how Apollo Client works is not a prerequisite for using the methods described here, but it may be helpful.
All the methods we will discuss can be called from the ApolloClient
class. Any code demonstration
in this article will assume that we have already initialized an instance of ApolloClient
and that
we have imported the gql
tag from graphql-tag
.
You can read more about Caching here
Updating the Cache after a Mutation
Being able to read and write to the Apollo cache from anywhere in your application gives you a lot
of power over your data. However, there is one place where we most often want to update our cached
data: after a mutation. As such, Apollo Client has optimized the experience for updating your cache
with the read and write methods after a mutation with the update
function. Let us say that we have
the following GraphQL mutation:
mutation TodoCreateMutation($text: String!) {
createTodo(text: $text) {
id
text
completed
}
}
We may also have the following GraphQL query:
query TodoAppQuery {
todos {
id
text
completed
}
}
At the end of our mutation we want our query to include the new todo like we had sent our
TodoAppQuery
a second time after the mutation finished without actually sending the query. To do
this we can use the update
function provided as an option of the Apollo.mutate
method. To update
your cache with the mutation just write code that looks like:
// We assume that the GraphQL operations `TodoCreateMutation` and
// `TodoAppQuery` have already been defined using the `gql` tag.
const text = 'Hello, world!';
@Component({ ... })
class AppComponent {
createTodo() {
this.apollo.mutate({
mutation: TodoCreateMutation,
variables: {
text,
},
update: (proxy, { data: { createTodo } }) => {
// Read the data from our cache for this query.
const data = proxy.readQuery({ query: TodoAppQuery });
// If you are using the Query service (TodoAppGQL) instead of defining your GQL as a constant, you can reference the query as:
// const data = proxy.readQuery({ query: this.todoAppGQL.document });
// Add our todo from the mutation to the end.
data.todos.push(createTodo);
// Write our data back to the cache.
proxy.writeQuery({ query: TodoAppQuery, data });
// alternatively when using Query service:
// proxy.writeQuery({ query: this.todoAppGQL.document, data });
},
}).subscribe();
}
}
The first proxy
argument is an instance of
DataProxy
(opens in a new tab) has
the same for methods that we just learned exist on the Apollo Client: readQuery
, readFragment
,
writeQuery
, and writeFragment
. The reason we call them on a proxy
object here instead of on
our client
instance is that we can easily apply optimistic updates (which we will demonstrate in a
bit). The proxy
object also provides an isolated transaction which shields you from any other
mutations going on at the same time, and the proxy
object also batches writes together until the
very end.
If you provide an optimisticResponse
option to the mutation then the update
function will be run
twice. Once immediately after you call apollo.mutate
with the data from optimisticResponse
.
After the mutation successfully executes against the server the changes made in the first call to
update
will be rolled back and update
will be called with the actual data returned by the
mutation and not just the optimistic response.
Putting it all together:
const text = 'Hello, world!';
@Component({ ... })
class AppComponent {
createTodo() {
this.apollo.mutate({
mutation: TodoCreateMutation,
variables: {
text,
},
optimisticResponse: {
id: -1, // -1 is a temporary id for the optimistic response.
text,
completed: false,
},
update: (proxy, { data: { createTodo } }) => {
const data = proxy.readQuery({ query: TodoAppQuery });
data.todos.push(createTodo);
proxy.writeQuery({ query: TodoAppQuery, data });
},
});
}
}
As you can see the update
function on apollo.mutate
provides extra change management
functionality specific to the use case of a mutation while still providing you the powerful data
control APIs that are available on Apollo
service.
The update
function is not a good place for side-effects as it may be called multiple times. Also,
you may not call any of the methods on proxy
asynchronously.