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),
) => {
  queryClient.setQueryData(getShowPetByIdQueryKey(petId), updater);
};

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. Orval will pipe each response through Schema.parse() inside the RxJS Observable pipeline before converting to a Promise:

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: true,
        },
      },
    },
    input: {
      target: './petstore.yaml',
    },
  },
});

This generates:

const request$ = http.get<Pets>(url, { params: httpParams })
  .pipe(map(data => Pets.parse(data)));

Validation is automatically skipped for primitive response types (string, void, etc.) and for operations using a custom mutator.

Full Example

See the Angular Query sample on GitHub.

On this page