Skip to content

Commit f5efa9f

Browse files
committed
feat(client): implement Angular HTTP client for API interactions
- Added client generation code to handle API requests using Angular's HttpClient. - Introduced utility functions for building URLs, merging configurations, and handling headers. - Implemented request and response interceptors for enhanced request handling. - Created types for request options, response styles, and client configuration. - Developed authentication handling with support for various auth schemes. - Added body and query parameter serialization utilities for request formatting. - Established a flexible configuration system for client initialization and request customization.
1 parent ee367da commit f5efa9f

File tree

20 files changed

+664
-434
lines changed

20 files changed

+664
-434
lines changed

examples/openapi-ts-angular/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
"typecheck": "tsc --noEmit"
1313
},
1414
"dependencies": {
15+
"@angular/common": "20.1.6",
16+
"@angular/core": "20.1.6",
1517
"@radix-ui/react-form": "0.1.1",
1618
"@radix-ui/react-icons": "1.3.2",
1719
"@radix-ui/themes": "3.1.6",
1820
"react": "19.0.0",
19-
"react-dom": "19.0.0"
21+
"react-dom": "19.0.0",
22+
"rxjs": "7.8.2"
2023
},
2124
"devDependencies": {
2225
"@config/vite-base": "workspace:*",
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import type { HttpResponse } from '@angular/common/http';
2+
import { HttpClient, HttpEventType, HttpRequest } from '@angular/common/http';
3+
import {
4+
assertInInjectionContext,
5+
inject,
6+
provideAppInitializer,
7+
} from '@angular/core';
8+
import { firstValueFrom } from 'rxjs';
9+
import { filter } from 'rxjs/operators';
10+
11+
import type { Client, Config, ResolvedRequestOptions } from './types.gen';
12+
import {
13+
buildUrl,
14+
createConfig,
15+
createInterceptors,
16+
mergeConfigs,
17+
mergeHeaders,
18+
setAuthParams,
19+
} from './utils.gen';
20+
21+
export function provideHeyApiClient(client: Client) {
22+
return provideAppInitializer(() => {
23+
const httpClient = inject(HttpClient);
24+
client.setConfig({ httpClient });
25+
});
26+
}
27+
28+
export const createClient = (config: Config = {}): Client => {
29+
let _config = mergeConfigs(createConfig(), config);
30+
31+
const getConfig = (): Config => ({ ..._config });
32+
33+
const setConfig = (config: Config): Config => {
34+
_config = mergeConfigs(_config, config);
35+
return getConfig();
36+
};
37+
38+
const interceptors = createInterceptors<
39+
HttpRequest<unknown>,
40+
HttpResponse<unknown>,
41+
unknown,
42+
ResolvedRequestOptions
43+
>();
44+
45+
const request: Client['request'] = async (options) => {
46+
const opts = {
47+
..._config,
48+
...options,
49+
headers: mergeHeaders(_config.headers, options.headers),
50+
httpClient: options.httpClient ?? _config.httpClient,
51+
serializedBody: undefined,
52+
};
53+
54+
if (!opts.httpClient) {
55+
assertInInjectionContext(request);
56+
opts.httpClient = inject(HttpClient);
57+
}
58+
59+
if (opts.security) {
60+
await setAuthParams({
61+
...opts,
62+
security: opts.security,
63+
});
64+
}
65+
66+
if (opts.requestValidator) {
67+
await opts.requestValidator(opts);
68+
}
69+
70+
if (opts.body && opts.bodySerializer) {
71+
opts.serializedBody = opts.bodySerializer(opts.body);
72+
}
73+
74+
// remove Content-Type header if body is empty to avoid sending invalid requests
75+
if (opts.serializedBody === undefined || opts.serializedBody === '') {
76+
opts.headers.delete('Content-Type');
77+
}
78+
79+
const url = buildUrl(opts);
80+
81+
let req = new HttpRequest<unknown>(opts.method, url, {
82+
redirect: 'follow',
83+
...opts,
84+
body: opts.serializedBody,
85+
});
86+
87+
for (const fn of interceptors.request._fns) {
88+
if (fn) {
89+
req = await fn(req, opts);
90+
}
91+
}
92+
93+
let response;
94+
const result = {
95+
request: req,
96+
response,
97+
};
98+
99+
try {
100+
response = await firstValueFrom(
101+
opts.httpClient
102+
.request(req)
103+
.pipe(filter((event) => event.type === HttpEventType.Response)),
104+
);
105+
106+
for (const fn of interceptors.response._fns) {
107+
if (fn) {
108+
response = await fn(response, req, opts);
109+
}
110+
}
111+
112+
let bodyResponse = response.body as Record<string, unknown>;
113+
114+
if (opts.responseValidator) {
115+
await opts.responseValidator(bodyResponse);
116+
}
117+
118+
if (opts.responseTransformer) {
119+
bodyResponse = (await opts.responseTransformer(bodyResponse)) as Record<
120+
string,
121+
unknown
122+
>;
123+
}
124+
125+
return (
126+
opts.responseStyle === 'data'
127+
? bodyResponse
128+
: { data: bodyResponse, ...result }
129+
) as any;
130+
} catch (error) {
131+
for (const fn of interceptors.error._fns) {
132+
if (fn) {
133+
(await fn(error, response!, req, opts)) as string;
134+
}
135+
}
136+
137+
return opts.responseStyle === 'data'
138+
? undefined
139+
: {
140+
error,
141+
...result,
142+
};
143+
}
144+
};
145+
146+
return {
147+
buildUrl,
148+
connect: (options) => request({ ...options, method: 'CONNECT' }),
149+
delete: (options) => request({ ...options, method: 'DELETE' }),
150+
get: (options) => request({ ...options, method: 'GET' }),
151+
getConfig,
152+
head: (options) => request({ ...options, method: 'HEAD' }),
153+
interceptors,
154+
options: (options) => request({ ...options, method: 'OPTIONS' }),
155+
patch: (options) => request({ ...options, method: 'PATCH' }),
156+
post: (options) => request({ ...options, method: 'POST' }),
157+
put: (options) => request({ ...options, method: 'PUT' }),
158+
request,
159+
setConfig,
160+
trace: (options) => request({ ...options, method: 'TRACE' }),
161+
};
162+
};

examples/openapi-ts-angular/src/client/client/client.ts

Lines changed: 0 additions & 193 deletions
This file was deleted.

0 commit comments

Comments
 (0)