Skip to content

Commit 9659bab

Browse files
committed
[typescript-angular] add support for httpResource
Add support for Angular httpResource. The support is added only for GET request as adviced by Angular docs: ``` TIP: Avoid using httpResource for mutations like POST or PUT. Instead, prefer directly using the underlying HttpClient APIs. ``` non-GET methods still rely on rxjs.
1 parent 6d7e8c6 commit 9659bab

36 files changed

+4120
-303
lines changed

docs/generators/typescript-angular.md

Lines changed: 42 additions & 41 deletions
Large diffs are not rendered by default.

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,15 @@ public boolean getHasVendorExtensions() {
297297
return nonEmpty(vendorExtensions);
298298
}
299299

300+
/**
301+
* Check if th httpMethod is a GET
302+
*
303+
* @return true if httpMethod is a GET, false otherwise
304+
*/
305+
public boolean isGet() {
306+
return "GET".equalsIgnoreCase(httpMethod);
307+
}
308+
300309
/**
301310
* Check if act as Restful index method
302311
*

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939
import java.io.File;
4040
import java.util.*;
41+
import java.util.function.Function;
4142
import java.util.stream.Collectors;
4243
import java.util.stream.Stream;
4344

@@ -83,6 +84,7 @@ public static enum PROVIDED_IN_LEVEL {none, root, any, platform}
8384
public static final String RXJS_VERSION = "rxjsVersion";
8485
public static final String NGPACKAGR_VERSION = "ngPackagrVersion";
8586
public static final String ZONEJS_VERSION = "zonejsVersion";
87+
public static final String USE_HTTP_RESOURCE = "useHttpResource";
8688

8789
protected String ngVersion = "20.0.0";
8890
@Getter @Setter
@@ -96,6 +98,7 @@ public static enum PROVIDED_IN_LEVEL {none, root, any, platform}
9698
@Getter protected Boolean stringEnums = false;
9799
protected QUERY_PARAM_OBJECT_FORMAT_TYPE queryParamObjectFormat = QUERY_PARAM_OBJECT_FORMAT_TYPE.dot;
98100
protected PROVIDED_IN_LEVEL providedIn = PROVIDED_IN_LEVEL.root;
101+
@Setter(AccessLevel.PRIVATE) private boolean useHttpResource = false;
99102

100103
private boolean taggedUnions = false;
101104

@@ -155,6 +158,7 @@ public TypeScriptAngularClientCodegen() {
155158
this.cliOptions.add(new CliOption(RXJS_VERSION, "The version of RxJS compatible with Angular (see ngVersion option)."));
156159
this.cliOptions.add(new CliOption(NGPACKAGR_VERSION, "The version of ng-packagr compatible with Angular (see ngVersion option)."));
157160
this.cliOptions.add(new CliOption(ZONEJS_VERSION, "The version of zone.js compatible with Angular (see ngVersion option)."));
161+
this.cliOptions.add(CliOption.newBoolean(USE_HTTP_RESOURCE, "Use httpResource to call GET endpoints").defaultValue(String.valueOf(this.useHttpResource)));
158162
}
159163

160164
@Override
@@ -310,6 +314,11 @@ public void processOpts() {
310314
additionalProperties.put("isQueryParamObjectFormatJson", getQueryParamObjectFormatJson());
311315
additionalProperties.put("isQueryParamObjectFormatKey", getQueryParamObjectFormatKey());
312316

317+
if (additionalProperties.containsKey(USE_HTTP_RESOURCE)) {
318+
this.setUseHttpResource(convertPropertyToBoolean(USE_HTTP_RESOURCE));
319+
}
320+
writePropertyBack(USE_HTTP_RESOURCE, getUseHttpResource());
321+
313322
}
314323

315324
@Data
@@ -434,7 +443,12 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L
434443

435444
// Prep a string buffer where we're going to set up our new version of the string.
436445
StringBuilder pathBuffer = new StringBuilder();
437-
ParameterExpander paramExpander = new ParameterExpander(op, this::toParamName);
446+
// If useHttpResource=true, means that we use signals and the variable is available under the name `this.toParamName(n) + "Value"`
447+
Function<String, String> paramName =
448+
isUseHttpResource(op)
449+
? n -> this.toParamName(n) + "Value"
450+
: this::toParamName;
451+
ParameterExpander paramExpander = new ParameterExpander(op, paramName);
438452
int insideCurly = 0;
439453

440454
// Iterate through existing string, one character at a time.
@@ -626,6 +640,10 @@ private String getApiFilenameFromClassname(String classname) {
626640
return toApiFilename(name);
627641
}
628642

643+
private boolean getUseHttpResource() {
644+
return useHttpResource;
645+
}
646+
629647
@Override
630648
public String toModelName(String name) {
631649
name = addSuffix(name, modelSuffix);
@@ -766,4 +784,8 @@ public void setProvidedIn(String level) {
766784
private boolean getIsProvidedInNone() {
767785
return PROVIDED_IN_LEVEL.none.equals(providedIn);
768786
}
787+
788+
private boolean isUseHttpResource(CodegenOperation operation) {
789+
return useHttpResource && operation.isGet();
790+
}
769791
}
Lines changed: 15 additions & 259 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{{>licenseInfo}}
22
/* tslint:disable:no-unused-variable member-ordering */
33

4-
import { Inject, Injectable, Optional } from '@angular/core';
4+
import { Inject, Injectable, Optional{{#useHttpResource}}, Signal{{/useHttpResource}} } from '@angular/core';
55
import { HttpClient, HttpHeaders, HttpParams,
6-
HttpResponse, HttpEvent{{#httpContextInOptions}}, HttpContext {{/httpContextInOptions}}
6+
HttpResponse, HttpEvent{{#httpContextInOptions}}, HttpContext {{/httpContextInOptions}}{{#useHttpResource}}, httpResource, HttpResourceFn, HttpResourceRef{{/useHttpResource}}
77
} from '@angular/common/http';
88
import { Observable } from 'rxjs';
99
import { OpenApiHttpParams, QueryParamStyle } from '../query.params';
@@ -67,263 +67,19 @@ export class {{classname}} extends BaseService {
6767
}
6868

6969
{{#operation}}
70-
/**
71-
{{#summary}}
72-
* {{.}}
73-
{{/summary}}
74-
{{#notes}}
75-
* {{.}}
76-
{{/notes}}
77-
* @endpoint {{httpMethod}} {{{vendorExtensions.x-path-from-spec}}}
78-
{{^useSingleRequestParameter}}
79-
{{#allParams}}
80-
* @param {{paramName}} {{description}}
81-
{{/allParams}}
82-
{{/useSingleRequestParameter}}
83-
{{#useSingleRequestParameter}}
84-
{{#allParams.0}}
85-
* @param requestParameters
86-
{{/allParams.0}}
87-
{{/useSingleRequestParameter}}
88-
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
89-
* @param reportProgress flag to report request and response progress.
90-
* @param options additional options
91-
{{#isDeprecated}}
92-
* @deprecated
93-
{{/isDeprecated}}
94-
*/
95-
public {{nickname}}({{^useSingleRequestParameter}}{{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}{{/useSingleRequestParameter}}{{#useSingleRequestParameter}}{{#allParams.0}}requestParameters{{^hasRequiredParams}}?{{/hasRequiredParams}}: {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams, {{/allParams.0}}{{/useSingleRequestParameter}}observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: {{#produces}}'{{{mediaType}}}'{{^-last}} | {{/-last}}{{/produces}}{{^produces}}undefined{{/produces}},{{#httpContextInOptions}} context?: HttpContext{{/httpContextInOptions}}{{#httpTransferCacheInOptions}}, transferCache?: boolean{{/httpTransferCacheInOptions}}}): Observable<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>;
96-
public {{nickname}}({{^useSingleRequestParameter}}{{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}{{/useSingleRequestParameter}}{{#useSingleRequestParameter}}{{#allParams.0}}requestParameters{{^hasRequiredParams}}?{{/hasRequiredParams}}: {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams, {{/allParams.0}}{{/useSingleRequestParameter}}observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: {{#produces}}'{{{mediaType}}}'{{^-last}} | {{/-last}}{{/produces}}{{^produces}}undefined{{/produces}},{{#httpContextInOptions}} context?: HttpContext{{/httpContextInOptions}}{{#httpTransferCacheInOptions}}, transferCache?: boolean{{/httpTransferCacheInOptions}}}): Observable<HttpResponse<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>>;
97-
public {{nickname}}({{^useSingleRequestParameter}}{{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}{{/useSingleRequestParameter}}{{#useSingleRequestParameter}}{{#allParams.0}}requestParameters{{^hasRequiredParams}}?{{/hasRequiredParams}}: {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams, {{/allParams.0}}{{/useSingleRequestParameter}}observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: {{#produces}}'{{{mediaType}}}'{{^-last}} | {{/-last}}{{/produces}}{{^produces}}undefined{{/produces}},{{#httpContextInOptions}} context?: HttpContext{{/httpContextInOptions}}{{#httpTransferCacheInOptions}}, transferCache?: boolean{{/httpTransferCacheInOptions}}}): Observable<HttpEvent<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>>;
98-
public {{nickname}}({{^useSingleRequestParameter}}{{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}{{/useSingleRequestParameter}}{{#useSingleRequestParameter}}{{#allParams.0}}requestParameters{{^hasRequiredParams}}?{{/hasRequiredParams}}: {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams, {{/allParams.0}}{{/useSingleRequestParameter}}observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: {{#produces}}'{{{mediaType}}}'{{^-last}} | {{/-last}}{{/produces}}{{^produces}}undefined{{/produces}},{{#httpContextInOptions}} context?: HttpContext{{/httpContextInOptions}}{{#httpTransferCacheInOptions}}, transferCache?: boolean{{/httpTransferCacheInOptions}}}): Observable<any> {
99-
{{#allParams}}
100-
{{#useSingleRequestParameter}}
101-
const {{paramName}} = requestParameters{{^hasRequiredVars}}?{{/hasRequiredVars}}.{{paramName}};
102-
{{/useSingleRequestParameter}}
103-
{{#required}}
104-
if ({{paramName}} === null || {{paramName}} === undefined) {
105-
throw new Error('Required parameter {{paramName}} was null or undefined when calling {{nickname}}.');
106-
}
107-
{{/required}}
108-
{{/allParams}}
109-
110-
{{#hasQueryParamsOrAuth}}
111-
let localVarQueryParameters = new OpenApiHttpParams(this.encoder);
112-
{{#queryParams}}
113-
114-
localVarQueryParameters = this.addToHttpParams(
115-
localVarQueryParameters,
116-
'{{baseName}}',
117-
<any>{{paramName}},
118-
{{#isQueryParamObjectFormatJson}}
119-
QueryParamStyle.Json,
120-
{{/isQueryParamObjectFormatJson}}
121-
{{^isQueryParamObjectFormatJson}}
122-
{{^style}}
123-
{{#queryIsJsonMimeType}}
124-
QueryParamStyle.Json,
125-
{{/queryIsJsonMimeType}}
126-
{{^queryIsJsonMimeType}}
127-
QueryParamStyle.Form,
128-
{{/queryIsJsonMimeType}}
129-
{{/style}}
130-
{{#style}}
131-
{{#isDeepObject}}
132-
QueryParamStyle.DeepObject,
133-
{{/isDeepObject}}
134-
{{#isFormStyle}}
135-
QueryParamStyle.Form,
136-
{{/isFormStyle}}
137-
{{#isSpaceDelimited}}
138-
QueryParamStyle.SpaceDelimited,
139-
{{/isSpaceDelimited}}
140-
{{#isPipeDelimited}}
141-
QueryParamStyle.PipeDelimited,
142-
{{/isPipeDelimited}}
143-
{{#queryIsJsonMimeType}}
144-
QueryParamStyle.Json,
145-
{{/queryIsJsonMimeType}}
146-
{{/style}}
147-
{{/isQueryParamObjectFormatJson}}
148-
{{isExplode}},
149-
);
150-
151-
{{/queryParams}}
152-
153-
{{/hasQueryParamsOrAuth}}
154-
let localVarHeaders = this.defaultHeaders;
155-
{{#headerParams}}
156-
{{#isArray}}
157-
if ({{paramName}}) {
158-
localVarHeaders = localVarHeaders.set('{{baseName}}', [...{{paramName}}].join(COLLECTION_FORMATS['{{collectionFormat}}']));
159-
}
160-
{{/isArray}}
161-
{{^isArray}}
162-
if ({{paramName}} !== undefined && {{paramName}} !== null) {
163-
localVarHeaders = localVarHeaders.set('{{baseName}}', String({{paramName}}));
164-
}
165-
{{/isArray}}
166-
{{/headerParams}}
167-
168-
{{#authMethods}}
169-
// authentication ({{name}}) required
170-
{{#isApiKey}}
171-
{{#isKeyInHeader}}
172-
localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', '{{keyParamName}}', localVarHeaders);
173-
{{/isKeyInHeader}}
174-
{{#isKeyInQuery}}
175-
localVarQueryParameters = this.configuration.addCredentialToQuery('{{name}}', '{{keyParamName}}', localVarQueryParameters);
176-
{{/isKeyInQuery}}
177-
{{/isApiKey}}
178-
{{#isBasic}}
179-
{{#isBasicBasic}}
180-
localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Basic ');
181-
{{/isBasicBasic}}
182-
{{#isBasicBearer}}
183-
localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Bearer ');
184-
{{/isBasicBearer}}
185-
{{/isBasic}}
186-
{{#isOAuth}}
187-
localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Bearer ');
188-
{{/isOAuth}}
189-
190-
{{/authMethods}}
191-
const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([
192-
{{#produces}}
193-
'{{{mediaType}}}'{{^-last}},{{/-last}}
194-
{{/produces}}
195-
]);
196-
if (localVarHttpHeaderAcceptSelected !== undefined) {
197-
localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected);
198-
}
199-
200-
{{#httpContextInOptions}}
201-
const localVarHttpContext: HttpContext = options?.context ?? new HttpContext();
202-
{{/httpContextInOptions}}
203-
{{#httpTransferCacheInOptions}}
204-
205-
const localVarTransferCache: boolean = options?.transferCache ?? true;
206-
{{/httpTransferCacheInOptions}}
207-
208-
{{#bodyParam}}
209-
{{- duplicated below, don't forget to change}}
210-
// to determine the Content-Type header
211-
const consumes: string[] = [
212-
{{#consumes}}
213-
'{{{mediaType}}}'{{^-last}},{{/-last}}
214-
{{/consumes}}
215-
];
216-
{{/bodyParam}}
217-
{{#hasFormParams}}
218-
{{^bodyParam}}
219-
// to determine the Content-Type header
220-
const consumes: string[] = [
221-
{{#consumes}}
222-
'{{{mediaType}}}'{{^-last}},{{/-last}}
223-
{{/consumes}}
224-
];
225-
{{/bodyParam}}
226-
{{/hasFormParams}}
227-
{{#bodyParam}}
228-
const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
229-
if (httpContentTypeSelected !== undefined) {
230-
localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected);
231-
}
232-
{{/bodyParam}}
233-
234-
{{#hasFormParams}}
235-
const canConsumeForm = this.canConsumeForm(consumes);
236-
237-
let localVarFormParams: { append(param: string, value: any): any; };
238-
let localVarUseForm = false;
239-
let localVarConvertFormParamsToString = false;
240-
{{#formParams}}
241-
{{#isFile}}
242-
// use FormData to transmit files using content-type "multipart/form-data"
243-
// see https://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data
244-
localVarUseForm = canConsumeForm;
245-
{{/isFile}}
246-
{{/formParams}}
247-
if (localVarUseForm) {
248-
localVarFormParams = new FormData();
249-
} else {
250-
localVarFormParams = new HttpParams({encoder: this.encoder});
251-
}
252-
253-
{{#formParams}}
254-
{{#isArray}}
255-
if ({{paramName}}) {
256-
{{#isCollectionFormatMulti}}
257-
{{paramName}}.forEach((element) => {
258-
localVarFormParams = localVarFormParams.append('{{baseName}}{{#useSquareBracketsInArrayNames}}[]{{/useSquareBracketsInArrayNames}}', <any>element) as any || localVarFormParams;
259-
})
260-
{{/isCollectionFormatMulti}}
261-
{{^isCollectionFormatMulti}}
262-
if (localVarUseForm) {
263-
{{paramName}}.forEach((element) => {
264-
localVarFormParams = localVarFormParams.append('{{baseName}}{{#useSquareBracketsInArrayNames}}[]{{/useSquareBracketsInArrayNames}}', <any>element) as any || localVarFormParams;
265-
})
266-
} else {
267-
localVarFormParams = localVarFormParams.append('{{baseName}}{{#useSquareBracketsInArrayNames}}[]{{/useSquareBracketsInArrayNames}}', [...{{paramName}}].join(COLLECTION_FORMATS['{{collectionFormat}}'])) as any || localVarFormParams;
268-
}
269-
{{/isCollectionFormatMulti}}
270-
}
271-
{{/isArray}}
272-
{{^isArray}}
273-
if ({{paramName}} !== undefined) {
274-
localVarFormParams = localVarFormParams.append('{{baseName}}', {{^isModel}}<any>{{paramName}}{{/isModel}}{{#isModel}}localVarUseForm ? new Blob([JSON.stringify({{paramName}})], {type: 'application/json'}) : <any>{{paramName}}{{/isModel}}) as any || localVarFormParams;
275-
}
276-
{{/isArray}}
277-
{{/formParams}}
278-
279-
{{/hasFormParams}}
280-
{{^isResponseFile}}
281-
let responseType_: 'text' | 'json' | 'blob' = 'json';
282-
if (localVarHttpHeaderAcceptSelected) {
283-
if (localVarHttpHeaderAcceptSelected.startsWith('text')) {
284-
responseType_ = 'text';
285-
} else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) {
286-
responseType_ = 'json';
287-
} else {
288-
responseType_ = 'blob';
289-
}
290-
}
291-
292-
{{/isResponseFile}}
293-
let localVarPath = `{{{path}}}`;
294-
const { basePath, withCredentials } = this.configuration;
295-
return this.httpClient.request{{^isResponseFile}}<{{#returnType}}{{{returnType}}}{{#isResponseTypeFile}}|undefined{{/isResponseTypeFile}}{{/returnType}}{{^returnType}}any{{/returnType}}>{{/isResponseFile}}('{{httpMethod}}', `${basePath}${localVarPath}`,
296-
{
297-
{{#httpContextInOptions}}
298-
context: localVarHttpContext,
299-
{{/httpContextInOptions}}
300-
{{#bodyParam}}
301-
body: {{paramName}},
302-
{{/bodyParam}}
303-
{{^bodyParam}}
304-
{{#hasFormParams}}
305-
body: localVarConvertFormParamsToString ? localVarFormParams.toString() : localVarFormParams,
306-
{{/hasFormParams}}
307-
{{/bodyParam}}
308-
{{#hasQueryParamsOrAuth}}
309-
params: localVarQueryParameters.toHttpParams(),
310-
{{/hasQueryParamsOrAuth}}
311-
{{#isResponseFile}}
312-
responseType: "blob",
313-
{{/isResponseFile}}
314-
{{^isResponseFile}}
315-
responseType: <any>responseType_,
316-
{{/isResponseFile}}
317-
...(withCredentials ? { withCredentials } : {}),
318-
headers: localVarHeaders,
319-
observe: observe,
320-
{{#httpTransferCacheInOptions}}
321-
...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}),
322-
{{/httpTransferCacheInOptions}}
323-
reportProgress: reportProgress
324-
}
325-
);
326-
}
70+
{{#useHttpResource}}
71+
{{#isGet}}
72+
{{> operationHttpResource }}
73+
{{/isGet}}
74+
{{/useHttpResource}}
75+
{{^useHttpResource}}
76+
{{> operationObservable }}
77+
{{/useHttpResource}}
78+
{{#useHttpResource}}
79+
{{^isGet}}
80+
{{> operationObservable }}
81+
{{/isGet}}
82+
{{/useHttpResource}}
32783

32884
{{/operation}}}
32985
{{/operations}}

0 commit comments

Comments
 (0)