Mutations
Prerequisites
This article assumes you're familiar with building basic GraphQL mutations. If you need a refresher, we recommend this guide.
This article also assumes that you've already set up your environment as per getting started guide.
Overview
mutate
is the primary method for modifying GraphQL back-end data in Apollo Orbit.
For a full documentation of mutations, please refer to the Apollo Client docs
mutate
mutate<TData, TVariables>(options: MutationOptions<TData, TVariables>): Observable<MutationResult<TData>>
Returns a MutationResult<TData, TVariables>
observable. This observable terminates with a result when the mutation is successful or an an error when it it fails.
The observable must be subscribed to for the mutation to execute.
Executing a mutation
To execute a mutation, inject Apollo
and pass it a GraphQL mutation document.
Let's look at an example.
First, we'll create a GraphQL mutation named UpdateBook
:
mutation UpdateBook($id: ID!, $book: BookInput!) {
updateBook(id: $id, book: $book) {
...BookFragment
}
}
Saving book.graphql will trigger codegen of UpdateBookMutation
class in /graphql/types.ts file as per our setup.
For more information about codegen and how it works, please refer to the Codegen section.
We have used the same BookFragment
from our previous query example, that way Apollo Client will automatically normalize and update the cache with the new data returned from the mutation.
This will automatically update any active QueryObservable
instances that are watching the same data, and the UI will display the new data without any additional effort by the developer.
Next, we execute the mutation and handle the result.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Apollo } from '@apollo-orbit/angular';
import { UpdateBookMutation } from '../graphql/types';
@Component({
selector: 'app-books',
templateUrl: './books.component.html',
styleUrls: ['./books.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BooksComponent {
protected readonly error = signal<Error | undefined>(undefined);
public constructor(
private readonly apollo: Apollo,
private readonly notificationService: NotificationService
) { }
...
protected updateBook(id: string, book: BookInput): void {
this.error.set(undefined);
this.apollo.mutate(new UpdateBookMutation({ id, book })).subscribe({
next: result => this.notificationService.success(`Book '${result.data!.updateBook.name}' was updated successfully.`),
error: (error) => this.error.set(error)
});
}
}
We apply non-null assertion !
to result.data
above because we know that, in this case, a successful mutation will always return data.
In some edge cases both data
and error
might be undefined, for example when errorPolicy
is set to ignore
.
mapMutation
Similar to mapQuery
, Apollo Orbit provides a mapMutation
RxJS operator to map the data
of a mutation result while preserving the other properties.
This is useful when you want to map the mutation result without dealing with the nullability of data
property.
import { mapMutation } from '@apollo-orbit/angular';
...
this.apollo.mutate(new UpdateBookMutation({ id, book })).pipe(
mapMutation(data => data.updateBook.name)
).subscribe({
next: name => this.notification.success(`Book '${name}' was updated successfully.`),
error: (error) => this.error.set(error)
});
Cache normalisation
As we've seen above, Apollo Client automatically updates the cache with the new data returned from the mutation.
But this might not be as straight forward with other mutations like addBook
or deleteBook
where the cache needs to be updated manually because Apollo Client cannot identify which parts of the cache reference the list of books.
For example, we might need to add a book to an author's books list and to the full list of books in cache.
This, including optimistic UI & refetching queries, will be covered in more detail in the State section of the docs.