# Angular Query

Generate [TanStack Query for Angular](https://tanstack.com/query/latest/docs/framework/angular/overview) injectable functions from your OpenAPI specification.

## Configuration

```ts
import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    output: {
      mode: 'tags-split',
      target: 'src/petstore.ts',
      schemas: 'src/model',
      client: 'angular-query',
      httpClient: 'angular',
      mock: true,
    },
    input: {
      target: './petstore.yaml',
    },
  },
});
```

## Generated Output

Orval generates injectable query functions using Angular's native `HttpClient`:

```ts
// Base operation function
export const showPetById = (
  http: HttpClient,
  petId: string,
  options?: { signal?: AbortSignal | null },
): Promise<Pet> => {
  const url = `/pets/${petId}`;
  const request$ = http.get<Pet>(url);
  if (options?.signal) {
    return lastValueFrom(
      request$.pipe(takeUntil(fromEvent(options.signal, 'abort'))),
    );
  }
  return lastValueFrom(request$);
};

// Query key factory
export const getShowPetByIdQueryKey = (petId?: string) =>
  [`/pets/${petId}`] as const;

// Injectable query function
export function injectShowPetById<TData = Pet, TError = unknown>(
  petId: string | (() => string),
  options?: { query?: Partial<CreateQueryOptions<Pet, TError, TData>> },
): CreateQueryResult<TData, TError> {
  const http = inject(HttpClient);
  const query = injectQuery(() => {
    const _petId = typeof petId === 'function' ? petId() : petId;
    return getShowPetByIdQueryOptions(http, _petId, options);
  });
  return query;
}
```

## Signal Reactivity

Parameters support Angular signals via getter functions:

```ts
@Component({...})
export class PetDetailComponent {
  petId = signal('1');

  // Query re-executes when petId() changes
  pet = injectShowPetById(() => this.petId());

  // Dynamic enabled/disabled
  conditionalPet = injectShowPetById(
    () => this.petId(),
    () => ({ query: { enabled: this.isEnabled() } }),
  );
}
```

## Mutations

For POST, PUT, PATCH, DELETE operations, Orval generates mutation options factories and injectable mutation functions:

```ts
// Mutation options factory
export const getCreatePetsMutationOptions = <TError = Error, TContext = unknown>(
  http: HttpClient,
  queryClient: QueryClient,
  options?: {
    mutation?: CreateMutationOptions<
      Awaited<ReturnType<typeof createPets>>,
      TError,
      { data: CreatePetsBody },
      TContext
    >;
    fetch?: RequestInit;
  },
) => {
  const mutationKey = ['createPets'];
  const mutationFn = async (props: { data: CreatePetsBody }) => {
    return createPets(http, props.data, options?.fetch);
  };

  return { mutationKey, mutationFn, ...options?.mutation };
};

// Injectable mutation function
export const injectCreatePets = <TError = Error, TContext = unknown>(
  options?: {
    mutation?: CreateMutationOptions<...>;
    fetch?: RequestInit;
  },
): CreateMutationResult<...> => {
  const http = inject(HttpClient);
  const queryClient = inject(QueryClient);
  const mutationOptions = getCreatePetsMutationOptions(http, queryClient, options);
  return injectMutation(() => mutationOptions);
};
```

Usage:

```ts
@Component({...})
export class CreatePetComponent {
  createPet = injectCreatePets();

  onSubmit(pet: CreatePetsBody) {
    this.createPet.mutate({ data: pet });
  }
}
```

## Query Invalidation

When `useInvalidate: true` is set, Orval generates helper functions to invalidate queries:

```ts
export const invalidateShowPetById = async (
  queryClient: QueryClient,
  petId: string,
  options?: InvalidateOptions,
): Promise<QueryClient> => {
  await queryClient.invalidateQueries(
    { queryKey: getShowPetByIdQueryKey(petId) },
    options,
  );
  return queryClient;
};
```

## Set Query Data

When `useSetQueryData: true` is set, Orval generates type-safe helper functions to update cached query data:

```ts
export const setShowPetByIdQueryData = (
  queryClient: QueryClient,
  petId: string,
  updater:
    | Awaited<ReturnType<typeof showPetById>>
    | undefined
    | ((
        old: Awaited<ReturnType<typeof showPetById>> | undefined,
      ) => Awaited<ReturnType<typeof showPetById>> | undefined),
) => {
  queryClient.setQueriesData<Awaited<ReturnType<typeof showPetById>>>(
    { queryKey: getShowPetByIdQueryKey(petId) },
    updater,
  );
};
```

The helper uses [`setQueriesData`](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientsetqueriesdata) so query keys are matched by prefix. For endpoints with query params or a body, those args are widened to accept `undefined` — pass `undefined` to update every cached entry sharing the same path; the updater is invoked once per matched entry.

> **Warning:**
> Prior to this change the helper called `setQueryData`, which writes (and creates) a single exact-key entry. `setQueriesData` only updates entries that already exist and matches by prefix, so calls with a fully-specified key may behave differently — review existing call sites when upgrading.

## Get Query Data

When `useGetQueryData: true` is set, Orval generates type-safe helper functions to read cached query data:

```ts
export const getListPetsQueryData = (
  queryClient: QueryClient,
  params: ListPetsParams,
) =>
  queryClient.getQueryData<Awaited<ReturnType<typeof listPets>>>(
    getListPetsQueryKey(params),
  );
```

## Automatic Mutation Invalidation

Configure automatic invalidation when mutations succeed:

```ts
import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    output: {
      client: 'angular-query',
      override: {
        query: {
          useInvalidate: true,
          mutationInvalidates: [
            {
              onMutations: ['createPets'],
              invalidates: ['listPets'],
            },
            {
              onMutations: ['deletePet', 'updatePet'],
              invalidates: [
                'listPets',
                { query: 'showPetById', params: ['petId'] },
              ],
            },
          ],
        },
      },
    },
  },
});
```

Usage:

```ts
@Component({
  template: `
    <ul>
      @for (pet of pets.data(); track pet.id) {
        <li>{{ pet.name }} <button (click)="onDelete(pet.id)">Delete</button></li>
      }
    </ul>
  `,
})
export class PetListComponent {
  pets = injectListPets();
  deletePet = injectDeletePet();

  onDelete(petId: string) {
    // listPets and showPetById(petId) are automatically invalidated
    this.deletePet.mutate({ petId });
  }
}
```

### Composing with user callbacks

When you provide your own `onSuccess`, both the auto-invalidation and your callback run:

```ts
deletePet = injectDeletePet({
  mutation: {
    onSuccess: () => this.snackbar.open('Pet deleted!'),
  },
  // invalidation still runs automatically before onSuccess
});
```

### Skipping auto-invalidation

Pass `skipInvalidation: true` to opt out of auto-invalidation at runtime — for example when you need to delay or conditionally run invalidation yourself:

```ts
deletePet = injectDeletePet({
  mutation: {
    onSuccess: (_data, variables) => {
      // Custom delayed invalidation
      setTimeout(() => {
        this.queryClient.invalidateQueries({
          queryKey: getListPetsQueryKey(),
        });
      }, 700);
    },
  },
  skipInvalidation: true,
});
```

## Query Options

Configure infinite queries and default options:

```ts
import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    output: {
      override: {
        query: {
          useQuery: true,
          useInfinite: true,
          useInfiniteQueryParam: 'nextId',
          options: {
            staleTime: 10000,
          },
        },
      },
    },
  },
});
```

## Per-Operation Configuration

You can override the `query` options for a single operation or tag:

```ts
import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    output: {
      override: {
        operations: {
          listPets: {
            query: {
              useInfinite: true,
              useInfiniteQueryParam: 'cursor',
              options: {
                staleTime: 60000,
              },
            },
          },
        },
      },
    },
  },
});
```

## Zod Runtime Validation

When using Zod schemas, you can enable automatic runtime validation of HTTP
responses. `runtimeValidation` accepts either:

- `true` (shorthand for `{ strategy: 'throw' }`) to parse and throw
- `{ strategy: 'both' }` to log the raw `ZodError` then throw

```ts
import { defineConfig } from 'orval';

export default defineConfig({
  petstore: {
    output: {
      client: 'angular-query',
      httpClient: 'angular',
      schemas: { path: 'src/model', type: 'zod' },
      override: {
        query: {
          runtimeValidation: { strategy: 'both' },
        },
      },
    },
    input: {
      target: './petstore.yaml',
    },
  },
});
```

This generates:

```ts
const request$ = http.get<PetsOutput>(url, { params: httpParams }).pipe(
  map((data) => {
    const result = Pets.safeParse(data);
    if (!result.success) {
      console.error('[orval] listPets response validation failed', result.error);
      throw result.error;
    }
    return result.data;
  }),
);
```

Use `runtimeValidation: true` to keep the existing `Schema.parse()` behavior.
Validation is automatically skipped for primitive response types (`string`,
`void`, etc.) and for operations using a custom mutator.

A validated request function is typed with the schema's `zod.output` alias
(`Promise<PetsOutput>` above), since that is what the parse returns at runtime —
relevant when the schema transforms values through `coerce`, `useDates`,
defaults or `.transform()`. Mutator operations keep the schema (input) type,
as the generated parse never runs there.

## Full Example

See the [Angular Query sample](https://github.com/orval-labs/orval/tree/master/samples/angular-query) on GitHub.
