import { Injector } from '@angular/core';
import { DefaultError } from '@tanstack/query-core';
import { CreateMutationOptions, CreateMutationResult } from './types.js';
export interface InjectMutationOptions {
/**
* The `Injector` in which to create the mutation.
*
* If this is not provided, the current injection context will be used instead (via `inject`).
*/
injector?: Injector;
}
/**
* Unlike queries, mutations are typically used to create/update/delete data or perform server side-effects.
* `injectMutation` is the function for that. Unlike queries, mutations are not run automatically.
*
* @remarks `mutate`/`mutateAsync` also accept per-call `onSuccess`/`onError`/`onSettled` callbacks as a
* second argument, useful for triggering call-site side effects (e.g. navigation) without coupling them to
* the shared mutation definition. Callbacks defined in `injectMutationFn` fire for every mutation; per-call
* callbacks fire only for the latest call you've made — `mutateAsync` gives you a promise per call instead,
* so you can await `Promise.all`/`Promise.allSettled` over several calls and see each one's outcome.
* @see {@link mutationOptions} to share these options across multiple `injectMutation` call sites, or to look
* the mutation up elsewhere via its `mutationKey` (e.g. with `injectMutationState`).
* @param injectMutationFn - A function that returns mutation options. Similar to `computed` from Angular,
* this function runs in the reactive context, so signals read inside it drive the mutation's options.
* @param options - Additional configuration
* @returns The mutation result. Value fields are exposed as a `Signal` — read `data`/`error` by calling them
* (e.g. `mutation.data()`) — while function fields (`mutate`, `mutateAsync`, `reset`) are called directly,
* unchanged. `isSuccess`/`isError`/`isPending`/`isIdle` are type-guard methods you can call to narrow whether
* `data` is defined.
*
* @example
* ```angular-ts
* @Component({
* selector: 'todos',
* template: `
* @if (addMutation.isPending()) {
* Adding todo...
* } @else if (addMutation.isError()) {
*
An error occurred: {{ addMutation.error()?.message }}
* }
*
* `,
* })
* export class Todos {
* readonly #queryClient = inject(QueryClient)
*
* readonly addMutation = injectMutation(() => ({
* mutationFn: addTodo,
* onSuccess: () => this.#queryClient.invalidateQueries({ queryKey: ['todos'] }),
* }))
* }
* ```
*
* @example
* Optimistic update via `onMutate`, rolling back on `onError`:
* ```angular-ts
* @Component({
* selector: 'todos',
* template: ``,
* })
* export class Todos {
* readonly #queryClient = inject(QueryClient)
*
* readonly addMutation = injectMutation(() => ({
* mutationFn: addTodo,
* onMutate: async (newTodo) => {
* await this.#queryClient.cancelQueries({ queryKey: ['todos'] })
* const previousTodos = this.#queryClient.getQueryData>(['todos'])
*
* this.#queryClient.setQueryData>(['todos'], (old) => [
* ...(old ?? []),
* newTodo,
* ])
*
* // Passed to `onError` as `onMutateResult` if the mutation fails.
* return { previousTodos }
* },
* onError: (_err, _newTodo, onMutateResult) => {
* this.#queryClient.setQueryData(['todos'], onMutateResult?.previousTodos)
* },
* onSettled: () => {
* this.#queryClient.invalidateQueries({ queryKey: ['todos'] })
* },
* }))
* }
* ```
*
* @example
* Callbacks passed per call to `mutate` only fire for the last call — `mutateAsync` gives you a promise per
* call instead, so you can wait for all of them when they succeed:
* ```angular-ts
* @Component({
* selector: 'todos',
* template: `
*
* `,
* })
* export class Todos {
* readonly #queryClient = inject(QueryClient)
*
* readonly addMutation = injectMutation(() => ({
* mutationFn: addTodo,
* onSuccess: () => this.#queryClient.invalidateQueries({ queryKey: ['todos'] }),
* }))
*
* async handleAddAll(todos: Array) {
* try {
* await Promise.all(todos.map((todo) => this.addMutation.mutateAsync(todo)))
* } catch (error) {
* console.error('Failed to add todos:', error)
* }
* }
* }
* ```
*
* @example
* If some of the mutations above can fail independently of the others, and you want to know which ones did —
* rather than losing that information the moment the first one rejects — swap `Promise.all` for
* `Promise.allSettled`:
* ```angular-ts
* @Component({
* selector: 'todos',
* template: `
*
* `,
* })
* export class Todos {
* readonly #queryClient = inject(QueryClient)
*
* readonly addMutation = injectMutation(() => ({
* mutationFn: addTodo,
* onSuccess: () => this.#queryClient.invalidateQueries({ queryKey: ['todos'] }),
* }))
*
* async handleAddAll(todos: Array) {
* const addResults = await Promise.allSettled(
* todos.map((todo) => this.addMutation.mutateAsync(todo)),
* )
*
* addResults.forEach((addResult, index) => {
* if (addResult.status === 'rejected') {
* console.error(`Failed to add "${todos[index]}":`, addResult.reason)
* }
* })
* }
* }
* ```
*/
export declare function injectMutation(injectMutationFn: () => CreateMutationOptions, options?: InjectMutationOptions): CreateMutationResult;