OrvalOrval

Angular Query

Generate TanStack Query for Angular

Generate TanStack Query for Angular injectable functions from your OpenAPI specification.

Configuration

orval.config.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:

// 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:

@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:

// 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:

@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:

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:

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

The helper uses setQueriesData so query keys can be matched by prefix. Pass $exactMatch: false to match and update every cached entry sharing the given path prefix. For endpoints with query params or a body, those args are widened to accept undefined — pass undefined (together with $exactMatch: false) to update every cached entry sharing the same path; the updater is invoked once per matched entry.

Prior to 8.11.0, the helper called setQueryData, which writes (and creates) a single exact-key entry. Since 8.11.0 the helper calls setQueriesData, which only updates entries that already exist (it never creates one), and as of this change it matches the key exactly by default. Pass $exactMatch: false to match by prefix instead. Review existing call sites when upgrading to confirm they still behave as expected.

Get Query Data

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

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

Automatic Mutation Invalidation

Configure automatic invalidation when mutations succeed:

orval.config.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:

@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:

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:

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:

orval.config.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:

orval.config.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
orval.config.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:

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 on GitHub.

On this page