Skip to content

Commit 5f92de4

Browse files
authored
[typescript-angular] refactor service classes for reduced bundle sizes. (#20681)
This reduces bundle sizes of ESM bundles on the order of 20%.
1 parent 9537a7f commit 5f92de4

File tree

105 files changed

+3297
-7789
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+3297
-7789
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ public void processOpts() {
182182
supportingFiles.add(new SupportingFile("index.mustache", getIndexDirectory(), "index.ts"));
183183
supportingFiles.add(new SupportingFile("api.module.mustache", getIndexDirectory(), "api.module.ts"));
184184
supportingFiles.add(new SupportingFile("configuration.mustache", getIndexDirectory(), "configuration.ts"));
185+
supportingFiles.add(new SupportingFile("api.base.service.mustache", getIndexDirectory(), "api.base.service.ts"));
185186
supportingFiles.add(new SupportingFile("variables.mustache", getIndexDirectory(), "variables.ts"));
186187
supportingFiles.add(new SupportingFile("encoder.mustache", getIndexDirectory(), "encoder.ts"));
187188
supportingFiles.add(new SupportingFile("param.mustache", getIndexDirectory(), "param.ts"));
@@ -411,7 +412,10 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L
411412
hasSomeFormParams = true;
412413
}
413414
op.httpMethod = op.httpMethod.toLowerCase(Locale.ENGLISH);
414-
415+
// deduplicate auth methods by name (as they will lead to duplicate code):
416+
op.authMethods =
417+
op.authMethods != null ? op.authMethods.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(x -> x.name))), ArrayList::new))
418+
: null;
415419

416420
// Prep a string buffer where we're going to set up our new version of the string.
417421
StringBuilder pathBuffer = new StringBuilder();
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { HttpClient, HttpHeaders, HttpParams, HttpParameterCodec } from '@angular/common/http';
2+
import { CustomHttpParameterCodec } from './encoder';
3+
import { {{configurationClassName}} } from './configuration';
4+
5+
export class BaseService {
6+
protected basePath = '';
7+
public defaultHeaders = new HttpHeaders();
8+
public configuration: {{configurationClassName}};
9+
public encoder: HttpParameterCodec;
10+
11+
constructor(protected httpClient: HttpClient, basePath?: string|string[], configuration?: {{configurationClassName}}) {
12+
this.configuration = configuration || new {{configurationClassName}}();
13+
if (typeof this.configuration.basePath !== 'string') {
14+
const firstBasePath = Array.isArray(basePath) ? basePath[0] : undefined;
15+
if (firstBasePath != undefined) {
16+
basePath = firstBasePath;
17+
}
18+
19+
if (typeof basePath !== 'string') {
20+
basePath = this.basePath;
21+
}
22+
this.configuration.basePath = basePath;
23+
}
24+
this.encoder = this.configuration.encoder || new CustomHttpParameterCodec();
25+
}
26+
27+
protected canConsumeForm(consumes: string[]): boolean {
28+
return consumes.indexOf('multipart/form-data') !== -1;
29+
}
30+
31+
protected addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams {
32+
// If the value is an object (but not a Date), recursively add its keys.
33+
if (typeof value === 'object' && !(value instanceof Date)) {
34+
return this.addToHttpParamsRecursive(httpParams, value, key);
35+
}
36+
return this.addToHttpParamsRecursive(httpParams, value, key);
37+
}
38+
39+
protected addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams {
40+
if (value === null || value === undefined) {
41+
return httpParams;
42+
}
43+
if (typeof value === 'object') {
44+
// If JSON format is preferred, key must be provided.
45+
if (key != null) {
46+
return httpParams.append(key, JSON.stringify(value));
47+
}
48+
// Otherwise, if it's an array, add each element.
49+
if (Array.isArray(value)) {
50+
value.forEach(elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key));
51+
} else if (value instanceof Date) {
52+
if (key != null) {
53+
httpParams = httpParams.append(key, value.toISOString());
54+
} else {
55+
throw Error("key may not be null if value is Date");
56+
}
57+
} else {
58+
Object.keys(value).forEach(k => {
59+
const paramKey = key ? `${key}.${k}` : k;
60+
httpParams = this.addToHttpParamsRecursive(httpParams, value[k], paramKey);
61+
});
62+
}
63+
return httpParams;
64+
} else if (key != null) {
65+
return httpParams.append(key, value);
66+
}
67+
throw Error("key may not be null if value is not object or array");
68+
}
69+
}

modules/openapi-generator/src/main/resources/typescript-angular/api.service.mustache

Lines changed: 19 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { {{ classname }} } from '{{ filename }}';
1616
// @ts-ignore
1717
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
1818
import { {{configurationClassName}} } from '../configuration';
19+
import { BaseService } from '../api.base.service';
1920
{{#withInterfaces}}
2021
import {
2122
{{classname}}Interface{{#useSingleRequestParameter}}{{#operations}}{{#operation}}{{#allParams.0}},
@@ -55,99 +56,14 @@ export interface {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterIn
5556
})
5657
{{/isProvidedInNone}}
5758
{{#withInterfaces}}
58-
export class {{classname}} implements {{classname}}Interface {
59+
export class {{classname}} extends BaseService implements {{classname}}Interface {
5960
{{/withInterfaces}}
6061
{{^withInterfaces}}
61-
export class {{classname}} {
62+
export class {{classname}} extends BaseService {
6263
{{/withInterfaces}}
6364

64-
protected basePath = '{{{basePath}}}';
65-
public defaultHeaders = new HttpHeaders();
66-
public configuration = new {{configurationClassName}}();
67-
public encoder: HttpParameterCodec;
68-
69-
constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string|string[], @Optional() configuration: {{configurationClassName}}) {
70-
if (configuration) {
71-
this.configuration = configuration;
72-
}
73-
if (typeof this.configuration.basePath !== 'string') {
74-
const firstBasePath = Array.isArray(basePath) ? basePath[0] : undefined;
75-
if (firstBasePath != undefined) {
76-
basePath = firstBasePath;
77-
}
78-
79-
if (typeof basePath !== 'string') {
80-
basePath = this.basePath;
81-
}
82-
this.configuration.basePath = basePath;
83-
}
84-
this.encoder = this.configuration.encoder || new CustomHttpParameterCodec();
85-
}
86-
87-
{{#hasSomeFormParams}}
88-
/**
89-
* @param consumes string[] mime-types
90-
* @return true: consumes contains 'multipart/form-data', false: otherwise
91-
*/
92-
private canConsumeForm(consumes: string[]): boolean {
93-
const form = 'multipart/form-data';
94-
for (const consume of consumes) {
95-
if (form === consume) {
96-
return true;
97-
}
98-
}
99-
return false;
100-
}
101-
{{/hasSomeFormParams}}
102-
103-
// @ts-ignore
104-
private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams {
105-
{{#isQueryParamObjectFormatJson}}
106-
httpParams = this.addToHttpParamsRecursive(httpParams, value, key);
107-
{{/isQueryParamObjectFormatJson}}
108-
{{^isQueryParamObjectFormatJson}}
109-
if (typeof value === "object" && value instanceof Date === false) {
110-
httpParams = this.addToHttpParamsRecursive(httpParams, value);
111-
} else {
112-
httpParams = this.addToHttpParamsRecursive(httpParams, value, key);
113-
}
114-
{{/isQueryParamObjectFormatJson}}
115-
return httpParams;
116-
}
117-
118-
private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams {
119-
if (value == null) {
120-
return httpParams;
121-
}
122-
123-
if (typeof value === "object") {
124-
{{#isQueryParamObjectFormatJson}}
125-
if (key != null) {
126-
httpParams = httpParams.append(key, JSON.stringify(value));
127-
} else {
128-
throw Error("key may not be null if value is a QueryParamObject");
129-
}
130-
{{/isQueryParamObjectFormatJson}}
131-
{{^isQueryParamObjectFormatJson}}
132-
if (Array.isArray(value)) {
133-
(value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key));
134-
} else if (value instanceof Date) {
135-
if (key != null) {
136-
httpParams = httpParams.append(key, (value as Date).toISOString(){{^isDateTime}}.substring(0, 10){{/isDateTime}});
137-
} else {
138-
throw Error("key may not be null if value is Date");
139-
}
140-
} else {
141-
Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive(
142-
httpParams, value[k], key != null ? `${key}{{#isQueryParamObjectFormatDot}}.{{/isQueryParamObjectFormatDot}}{{#isQueryParamObjectFormatKey}}[{{/isQueryParamObjectFormatKey}}${k}{{#isQueryParamObjectFormatKey}}]{{/isQueryParamObjectFormatKey}}` : k));
143-
}
144-
{{/isQueryParamObjectFormatJson}}
145-
} else if (key != null) {
146-
httpParams = httpParams.append(key, value);
147-
} else {
148-
throw Error("key may not be null if value is not object or array");
149-
}
150-
return httpParams;
65+
constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string|string[], @Optional() configuration?: {{configurationClassName}}) {
66+
super(httpClient, basePath, configuration);
15167
}
15268

15369
{{#operation}}
@@ -213,10 +129,8 @@ export class {{classname}} {
213129
}
214130
{{/isArray}}
215131
{{^isArray}}
216-
if ({{paramName}} !== undefined && {{paramName}} !== null) {
217-
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
218-
<any>{{paramName}}, '{{baseName}}');
219-
}
132+
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
133+
<any>{{paramName}}, '{{baseName}}');
220134
{{/isArray}}
221135
{{/queryParams}}
222136

@@ -236,60 +150,43 @@ export class {{classname}} {
236150
{{/headerParams}}
237151

238152
{{#authMethods}}
239-
{{#-first}}
240-
let localVarCredential: string | undefined;
241-
{{/-first}}
242153
// authentication ({{name}}) required
243-
localVarCredential = this.configuration.lookupCredential('{{name}}');
244-
if (localVarCredential) {
245154
{{#isApiKey}}
246155
{{#isKeyInHeader}}
247-
localVarHeaders = localVarHeaders.set('{{keyParamName}}', localVarCredential);
156+
localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', '{{keyParamName}}', localVarHeaders);
248157
{{/isKeyInHeader}}
249158
{{#isKeyInQuery}}
250-
localVarQueryParameters = localVarQueryParameters.set('{{keyParamName}}', localVarCredential);
159+
localVarQueryParameters = this.configuration.addCredentialToQuery('{{name}}', '{{keyParamName}}', localVarQueryParameters);
251160
{{/isKeyInQuery}}
252161
{{/isApiKey}}
253162
{{#isBasic}}
254163
{{#isBasicBasic}}
255-
localVarHeaders = localVarHeaders.set('Authorization', 'Basic ' + localVarCredential);
164+
localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Basic ');
256165
{{/isBasicBasic}}
257166
{{#isBasicBearer}}
258-
localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential);
167+
localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Bearer ');
259168
{{/isBasicBearer}}
260169
{{/isBasic}}
261170
{{#isOAuth}}
262-
localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential);
171+
localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Bearer ');
263172
{{/isOAuth}}
264-
}
265173

266174
{{/authMethods}}
267-
let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
268-
if (localVarHttpHeaderAcceptSelected === undefined) {
269-
// to determine the Accept header
270-
const httpHeaderAccepts: string[] = [
271-
{{#produces}}
272-
'{{{mediaType}}}'{{^-last}},{{/-last}}
273-
{{/produces}}
274-
];
275-
localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
276-
}
175+
const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([
176+
{{#produces}}
177+
'{{{mediaType}}}'{{^-last}},{{/-last}}
178+
{{/produces}}
179+
]);
277180
if (localVarHttpHeaderAcceptSelected !== undefined) {
278181
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
279182
}
280183

281184
{{#httpContextInOptions}}
282-
let localVarHttpContext: HttpContext | undefined = options && options.context;
283-
if (localVarHttpContext === undefined) {
284-
localVarHttpContext = new HttpContext();
285-
}
185+
const localVarHttpContext: HttpContext = options?.context ?? new HttpContext();
286186
{{/httpContextInOptions}}
287187
{{#httpTransferCacheInOptions}}
288188

289-
let localVarTransferCache: boolean | undefined = options && options.transferCache;
290-
if (localVarTransferCache === undefined) {
291-
localVarTransferCache = true;
292-
}
189+
const localVarTransferCache: boolean = options?.transferCache ?? true;
293190
{{/httpTransferCacheInOptions}}
294191

295192
{{#bodyParam}}

modules/openapi-generator/src/main/resources/typescript-angular/configuration.mustache

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HttpParameterCodec } from '@angular/common/http';
1+
import { HttpHeaders, HttpParams, HttpParameterCodec } from '@angular/common/http';
22
import { Param } from './param';
33

44
export interface {{configurationParametersInterfaceName}} {
@@ -187,6 +187,20 @@ export class {{configurationClassName}} {
187187
: value;
188188
}
189189

190+
public addCredentialToHeaders(credentialKey: string, headerName: string, headers: HttpHeaders, prefix?: string): HttpHeaders {
191+
const value = this.lookupCredential(credentialKey);
192+
return value
193+
? headers.set(headerName, (prefix ?? '') + value)
194+
: headers;
195+
}
196+
197+
public addCredentialToQuery(credentialKey: string, paramName: string, query: HttpParams): HttpParams {
198+
const value = this.lookupCredential(credentialKey);
199+
return value
200+
? query.set(paramName, value)
201+
: query;
202+
}
203+
190204
private defaultEncodeParam(param: Param): string {
191205
// This implementation exists as fallback for missing configuration
192206
// and for backwards compatibility to older typescript-angular generator versions.

modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.testng.annotations.Test;
1717

1818
import java.io.File;
19+
import java.io.IOException;
1920
import java.nio.file.Files;
2021
import java.nio.file.Paths;
2122
import java.util.HashMap;
@@ -390,4 +391,28 @@ public void testAngularDependenciesFromConfigFile() {
390391
assertThat(codegen.additionalProperties()).containsEntry("ngPackagrVersion", "19.0.0");
391392
assertThat(codegen.additionalProperties()).containsEntry("zonejsVersion", "0.15.0");
392393
}
394+
395+
@Test
396+
public void testNoDuplicateAuthentication() throws IOException {
397+
// GIVEN
398+
final String specPath = "src/test/resources/3_0/spring/petstore-auth.yaml";
399+
400+
File output = Files.createTempDirectory("test").toFile();
401+
output.deleteOnExit();
402+
403+
// WHEN
404+
final CodegenConfigurator configurator = new CodegenConfigurator()
405+
.setGeneratorName("typescript-angular")
406+
.setInputSpec(specPath)
407+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
408+
409+
final ClientOptInput clientOptInput = configurator.toClientOptInput();
410+
411+
Generator generator = new DefaultGenerator();
412+
generator.opts(clientOptInput).generate();
413+
414+
// THEN
415+
final String fileContents = Files.readString(Paths.get(output + "/api/default.service.ts"));
416+
assertThat(fileContents).containsOnlyOnce("localVarHeaders = this.configuration.addCredentialToHeaders('OAuth2', 'Authorization', localVarHeaders, 'Bearer ');");
417+
}
393418
}

samples/client/others/typescript-angular/builds/composed-schemas-tagged-unions/.openapi-generator/FILES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.gitignore
22
README.md
3+
api.base.service.ts
34
api.module.ts
45
api/api.ts
56
api/pet.service.ts

0 commit comments

Comments
 (0)