Type-safe Angular resource signals and HTTP client backed by openapi-typescript.
This library provides two main utilities:
createOpenApiResource— a reactive factory built on Angular'shttpResourcethat gives you compile-time type safety for every HTTP request and response shape defined in your OpenAPI schema.OpenApiHttpClient— a typed wrapper around Angular'sHttpClientfor imperative (non-reactive) HTTP calls.
Both are driven by the types generated by openapi-typescript, so typos in paths, wrong parameter names, or incorrect body shapes are caught at compile time, not at runtime.
npm install ngx-openapi-typescript openapi-typescript-helpersThis package is built for Angular ^21.2.0 and ^22.0.0 and expects these peer dependencies in your app:
@angular/common ^21.2.0 || ^22.0.0@angular/core ^21.2.0 || ^22.0.0openapi-typescript-helpers ^0.1.0
If you want to generate types locally, also install the generator as a dev dependency:
npm install -D openapi-typescriptGenerate type definitions by pointing openapi-typescript at your OpenAPI spec:
npx openapi-typescript https://api.example.com/openapi.yaml -o src/api.types.tsThis creates a paths interface that both utilities consume.
A factory for creating reactive Angular resource signals. Must be called inside an Angular injection context (component/service field initializer or constructor).
import type { paths } from './api.types';
import { createOpenApiResource } from 'ngx-openapi-typescript';
@Component({ ... })
export class PetListComponent {
private readonly api = createOpenApiResource<paths>({
baseUrl: 'https://api.example.com',
});
// Simple GET — no params required
protected readonly pets = this.api('/pets');
// Reactive GET with params — re-fetches when the signals read inside the lambda change
protected readonly petsLimited = this.api('/pets', () => ({
params: { query: { limit: 10 } },
}));
// Pass signal-driven path params — re-fetches when petId() changes
private readonly petId = signal(1);
protected readonly pet = this.api('/pets/{petId}', () => ({
params: { path: { petId: this.petId() } },
}));
// Return undefined to put the resource in Idle state (no request made)
private readonly enabled = signal(false);
protected readonly lazyPets = this.api('/pets', () =>
this.enabled() ? {} : undefined,
);
}The factory defaults to GET. You can pass a method option to use a different HTTP method,
but Angular recommends using httpResource only for reads. Prefer OpenApiHttpClient for mutations.
// Explicit method override (not recommended for production use)
protected readonly createResult = this.api('/pets', () => ({
body: { name: 'Buddy' },
}), { method: 'post' });A base class that wraps Angular's HttpClient with OpenAPI types. Ideal for imperative calls, mutations, or service-layer code.
import type { paths } from './api.types';
import { OpenApiHttpClient } from 'ngx-openapi-typescript';
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class PetApiClient extends OpenApiHttpClient<paths> {
constructor() {
super({ baseUrl: 'https://api.example.com' });
}
}// In a component/service
@Component({ ... })
export class PetDetailComponent {
private readonly api = inject(PetApiClient);
load(id: number) {
this.api
.get('/pets/{petId}', { params: { path: { petId: id } } })
.subscribe(pet => console.log(pet)); // pet is typed as Pet
}
create(name: string) {
this.api
.post('/pets', { body: { name } })
.subscribe(newPet => console.log(newPet));
}
}By default, OpenApiHttpClient injects Angular's configured HttpClient and uses
its interceptors. If you need a second API client with a differently configured
HttpClient, pass that instance through the httpClient option. Both clients
retain the same generated endpoint, parameter, body, and response types.
For example, inside an Angular injection context:
import { HttpBackend, HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { OpenApiHttpClient } from 'ngx-openapi-typescript';
import type { paths } from './api.types';
// Uses Angular's default HttpClient; requires an injection context.
const api = new OpenApiHttpClient<paths>({
baseUrl: 'https://api.example.com',
});
// Uses a separate HttpClient instance.
const secondApi = new OpenApiHttpClient<paths>({
baseUrl: 'https://second-api.example.com',
httpClient: new HttpClient(inject(HttpBackend)),
});When you supply an existing HttpClient instance, OpenApiHttpClient can also be
constructed outside an Angular injection context. The example above requires one
because it calls inject(HttpBackend).
createOpenApiResource cannot accept a custom HttpClient, because Angular's
underlying httpResource does not offer this option. See
Angular issue #62616.
OpenApiHttpClient follows Angular's responseType and observe options on
get, post, put, patch, and delete.
responseType |
Response body type |
|---|---|
omitted or 'json' |
Generated OpenAPI success-response type |
'blob' |
Blob |
'arraybuffer' |
ArrayBuffer |
'text' |
string |
observe |
Return type |
|---|---|
omitted or 'body' |
Observable<Body> |
'response' |
Observable<HttpResponse<Body>> |
'events' |
Observable<HttpEvent<Body>> |
For example, an endpoint whose schema declares application/pdf with
type: string and format: binary can be read as a blob:
api.get('/documents/{id}', {
params: { path: { id: 123 } },
responseType: 'blob',
}); // Observable<Blob>
api
.get('/documents/{id}', {
params: { path: { id: 123 } },
responseType: 'blob',
observe: 'response',
})
.subscribe((response) => {
const disposition = response.headers.get('Content-Disposition');
const pdf = response.body; // Blob | null, as in Angular's HttpResponse
});Use events and reportProgress when you need download progress:
import { HttpEventType } from '@angular/common/http';
api
.get('/documents/{id}', {
params: { path: { id: 123 } },
responseType: 'blob',
observe: 'events',
reportProgress: true,
})
.subscribe((event) => {
if (event.type === HttpEventType.DownloadProgress) {
console.log(event.loaded, event.total); // total can be undefined
} else if (event.type === HttpEventType.Response) {
console.log(event.body); // Blob | null
}
});Raw response formats are available on any valid endpoint, including JSON endpoints. The selected decoder determines the body type, while paths, parameters, and request bodies remain checked against the generated schema.
The factory returned by createOpenApiResource mirrors Angular's resource
sub-functions.
const resource = createOpenApiResource<paths>({ baseUrl: '/api' });
const pdf = resource.blob('/documents/{id}', () => ({
params: { path: { id: documentId() } },
reportProgress: true,
})); // HttpResourceRef<Blob | undefined>
const text = resource.text('/export');
// HttpResourceRef<string | undefined>
const bytes = resource.arrayBuffer('/export');
// HttpResourceRef<ArrayBuffer | undefined>Each variant supports the same optional request callback, method override, and
idle state (undefined from the callback) as the JSON factory. Resources expose
headers(), statusCode(), and progress() directly, so they do not take an
observe option.
parse receives the raw decoded type and can synchronously transform it.
A defined defaultValue removes the implicit undefined from the value type:
const length = resource.text('/export', {
parse: (text) => text.length,
defaultValue: 0,
}); // HttpResourceRef<number>
const size = resource.blob(
'/documents/{id}',
() => ({
params: { path: { id: documentId() } },
}),
{
parse: (blob) => blob.size,
},
); // HttpResourceRef<number | undefined># Build the library
ng build ngx-openapi-typescript
# Run tests
ng test ngx-openapi-typescript
# Check compile-time API assertions explicitly
npx tsc -p projects/ngx-openapi-typescript/tsconfig.spec.json --noEmit
# Start the demo app
ng serve demo