Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bin/configs/typescript-angular-v20-query-param-form-dot.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
generatorName: typescript-angular
outputDir: samples/client/others/typescript-angular-v20/builds/query-param-form-dot
inputSpec: modules/openapi-generator/src/test/resources/3_0/query-param-form.yaml
templateDir: modules/openapi-generator/src/main/resources/typescript-angular
additionalProperties:
ngVersion: 20.0.0
npmName: sample-angular-20-0-0-query-param-form-dot
supportsES6: true
useDotNotationForFormObjects: true
1 change: 1 addition & 0 deletions docs/generators/typescript-angular.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|supportsES6|Generate code that conforms to ES6.| |false|
|taggedUnions|Use discriminators to create tagged unions instead of extending interfaces.| |false|
|tsVersion|The version of typescript compatible with Angular (see ngVersion option).| |null|
|useDotNotationForFormObjects|Use parameter-prefixed dot notation for exploded form query objects instead of the OpenAPI standard unprefixed property names. Enable only for servers requiring this convention.| |false|
|useSingleRequestParameter|Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.| |false|
|useSquareBracketsInArrayNames|Setting this property to true will add brackets to array attribute names, e.g. my_values[].| |false|
|withInterfaces|Setting this property to true will generate interfaces next to the default class implementations.| |false|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public static enum PROVIDED_IN_LEVEL {none, root, any, platform}
public static final String FILE_NAMING = "fileNaming";
public static final String STRING_ENUMS = "stringEnums";
public static final String STRING_ENUMS_DESC = "Generate string enums instead of objects for enum values.";
public static final String USE_DOT_NOTATION_FOR_FORM_OBJECTS = "useDotNotationForFormObjects";
public static final String QUERY_PARAM_OBJECT_FORMAT = "queryParamObjectFormat";
public static final String USE_SQUARE_BRACKETS_IN_ARRAY_NAMES = "useSquareBracketsInArrayNames";
public static final String TS_VERSION = "tsVersion";
Expand Down Expand Up @@ -152,6 +153,7 @@ public TypeScriptAngularClientCodegen() {
this.cliOptions.add(new CliOption(MODEL_FILE_SUFFIX, "The suffix of the file of the generated model (model<suffix>.ts)."));
this.cliOptions.add(new CliOption(FILE_NAMING, "Naming convention for the output files: 'camelCase', 'kebab-case'.").defaultValue(this.fileNaming));
this.cliOptions.add(new CliOption(STRING_ENUMS, STRING_ENUMS_DESC).defaultValue(String.valueOf(this.stringEnums)));
this.cliOptions.add(CliOption.newBoolean(USE_DOT_NOTATION_FOR_FORM_OBJECTS, "Use parameter-prefixed dot notation for exploded form query objects instead of the OpenAPI standard unprefixed property names. Enable only for servers requiring this convention.", false));
this.cliOptions.add(new CliOption(QUERY_PARAM_OBJECT_FORMAT, "The format for query param objects: 'dot', 'json', 'key'.").defaultValue(this.queryParamObjectFormat.name()));
this.cliOptions.add(CliOption.newBoolean(USE_SQUARE_BRACKETS_IN_ARRAY_NAMES, "Setting this property to true will add brackets to array attribute names, e.g. my_values[].", false));
this.cliOptions.add(new CliOption(TS_VERSION, "The version of typescript compatible with Angular (see ngVersion option)."));
Expand Down Expand Up @@ -306,6 +308,9 @@ public void processOpts() {
this.setFileNaming(additionalProperties.get(FILE_NAMING).toString());
}

additionalProperties.put(USE_DOT_NOTATION_FOR_FORM_OBJECTS, additionalProperties.containsKey(USE_DOT_NOTATION_FOR_FORM_OBJECTS)
&& convertPropertyToBoolean(USE_DOT_NOTATION_FOR_FORM_OBJECTS));

if (additionalProperties.containsKey(QUERY_PARAM_OBJECT_FORMAT)) {
setQueryParamObjectFormat((String) additionalProperties.get(QUERY_PARAM_OBJECT_FORMAT));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,20 @@ new Configuration({
[parameter-locations-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-locations
[style-values-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-values
[@honoluluhenk/http-param-expander]: https://www.npmjs.com/package/@honoluluhenk/http-param-expander
{{#useDotNotationForFormObjects}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new dot-notation documentation is only added to README.mustache, which is used only when ngVersion >= 17. Clients generated with pre-v17 templates (README_beforeV17.mustache) still get the useDotNotationForFormObjects behavior from the shared api.base.service.mustache, but their README.md never documents it. Add the equivalent section to README_beforeV17.mustache so both template variants document the option.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/resources/typescript-angular/README.mustache, line 186:

<comment>The new dot-notation documentation is only added to README.mustache, which is used only when ngVersion >= 17. Clients generated with pre-v17 templates (README_beforeV17.mustache) still get the `useDotNotationForFormObjects` behavior from the shared api.base.service.mustache, but their README.md never documents it. Add the equivalent section to README_beforeV17.mustache so both template variants document the option.</comment>

<file context>
@@ -183,3 +183,17 @@ new Configuration({
 [parameter-locations-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-locations
 [style-values-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-values
 [@honoluluhenk/http-param-expander]: https://www.npmjs.com/package/@honoluluhenk/http-param-expander
+{{#useDotNotationForFormObjects}}
+
+## Form query object dot notation
</file context>


## Form query object dot notation

This client was generated with `useDotNotationForFormObjects=true`. For exploded
form query objects, property names include the parameter name and all parent
properties: `filter = { name: { contains: "Alice" } }` is serialized as
`filter.name.contains=Alice`. Primitive arrays and sets retain repeated keys,
for example `filter.ids=1&filter.ids=2`.

This is a server-specific convention, not standard OpenAPI form serialization.
The option defaults to false and does not affect non-exploded form parameters,
JSON, deepObject, spaceDelimited or pipeDelimited parameters.
{{/useDotNotationForFormObjects}}

## Deep-object query parameters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class BaseService {
if (paramStyle === QueryParamStyle.Form) {
if (explode) {
Object.keys(value).forEach(k => {
httpParams = this.addToHttpParams(httpParams, k, value[k], paramStyle, explode);
httpParams = this.addToHttpParams(httpParams, {{#useDotNotationForFormObjects}}`${key}.${k}`{{/useDotNotationForFormObjects}}{{^useDotNotationForFormObjects}}k{{/useDotNotationForFormObjects}}, value[k], paramStyle, explode);
});
return httpParams;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public Map<String, String> createOptions() {
.put(TypeScriptAngularClientCodegen.MODEL_SUFFIX, MODEL_SUFFIX)
.put(TypeScriptAngularClientCodegen.MODEL_FILE_SUFFIX, MODEL_FILE_SUFFIX)
.put(TypeScriptAngularClientCodegen.FILE_NAMING, FILE_NAMING_VALUE)
.put(TypeScriptAngularClientCodegen.USE_DOT_NOTATION_FOR_FORM_OBJECTS, Boolean.FALSE.toString())
.put(TypeScriptAngularClientCodegen.QUERY_PARAM_OBJECT_FORMAT, QUERY_PARAM_OBJECT_FORMAT_VALUE)
.put(TypeScriptAngularClientCodegen.USE_SQUARE_BRACKETS_IN_ARRAY_NAMES, Boolean.FALSE.toString())
.put(TypeScriptAngularClientCodegen.TS_VERSION, TS_VERSION)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,29 @@ public void testEnumAsConst() throws IOException {
assertThat(fileContents).doesNotContain(" as Type");
}

@Test
public void testFormObjectDotNotationOption() throws IOException {
for (Object option : new Object[]{null, false, "false", true, "true"}) {
File output = Files.createTempDirectory("angular-form-dot").toFile();
output.deleteOnExit();
CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("typescript-angular")
.setInputSpec("src/test/resources/3_0/query-param-form.yaml")
.setOutputDir(output.getAbsolutePath());
if (option != null) {
configurator.addAdditionalProperty(TypeScriptAngularClientCodegen.USE_DOT_NOTATION_FOR_FORM_OBJECTS, option);
}
new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
String service = Files.readString(output.toPath().resolve("api.base.service.ts"));
if (Boolean.parseBoolean(String.valueOf(option))) {
assertThat(service).contains("this.addToHttpParams(httpParams, `${key}.${k}`, value[k], paramStyle, explode)");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: In the enabled (true) branch, the test asserts only contains for the dot-notation template, but the disabled branch also asserts doesNotContain for the opposite variant. Add the symmetric negative assertion so a regression that emits both the plain k and the `${key}.${k}` form fails the test.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java, line 495:

<comment>In the enabled (`true`) branch, the test asserts only `contains` for the dot-notation template, but the disabled branch also asserts `doesNotContain` for the opposite variant. Add the symmetric negative assertion so a regression that emits both the plain `k` and the `` `${key}.${k}` `` form fails the test.</comment>

<file context>
@@ -477,6 +477,29 @@ public void testEnumAsConst() throws IOException {
+            new DefaultGenerator().opts(configurator.toClientOptInput()).generate();
+            String service = Files.readString(output.toPath().resolve("api.base.service.ts"));
+            if (Boolean.parseBoolean(String.valueOf(option))) {
+                assertThat(service).contains("this.addToHttpParams(httpParams, `${key}.${k}`, value[k], paramStyle, explode)");
+            } else {
+                assertThat(service).contains("this.addToHttpParams(httpParams, k, value[k], paramStyle, explode)");
</file context>
Suggested change
assertThat(service).contains("this.addToHttpParams(httpParams, `${key}.${k}`, value[k], paramStyle, explode)");
assertThat(service).contains("this.addToHttpParams(httpParams, `${key}.${k}`, value[k], paramStyle, explode)");
assertThat(service).doesNotContain("this.addToHttpParams(httpParams, k, value[k], paramStyle, explode)");

} else {
assertThat(service).contains("this.addToHttpParams(httpParams, k, value[k], paramStyle, explode)");
assertThat(service).doesNotContain("`${key}.${k}`");
}
}
}

@Test
public void testDeepObject() throws IOException {
// GIVEN
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
wwwroot/*.js
node_modules
typings
dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.gitignore
README.md
api.base.service.ts
api.module.ts
api/api.ts
api/default.service.ts
configuration.ts
encoder.ts
git_push.sh
index.ts
model/filter.ts
model/item.ts
model/models.ts
model/response.ts
ng-package.json
package.json
param.ts
provide-api.ts
query.params.ts
tsconfig.json
variables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.26.0-SNAPSHOT
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# sample-angular-20-0-0-query-param-form-dot@1.0.0

No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)

The version of the OpenAPI document: 1.0.0

## Building

To install the required dependencies and to build the typescript sources run:

```console
npm install
npm run build
```

## Publishing

First build the package then run `npm publish dist` (don't forget to specify the `dist` folder!)

## Consuming

Navigate to the folder of your consuming project and run one of next commands.

_published:_

```console
npm install sample-angular-20-0-0-query-param-form-dot@1.0.0 --save
```

_without publishing (not recommended):_

```console
npm install PATH_TO_GENERATED_PACKAGE/dist.tgz --save
```

_It's important to take the tgz file, otherwise you'll get trouble with links on windows_

_using `npm link`:_

In PATH_TO_GENERATED_PACKAGE/dist:

```console
npm link
```

In your project:

```console
npm link sample-angular-20-0-0-query-param-form-dot
```

__Note for Windows users:__ The Angular CLI has troubles to use linked npm packages.
Please refer to this issue <https://github.com/angular/angular-cli/issues/8284> for a solution / workaround.
Published packages are not effected by this issue.

### General usage

In your Angular project:

```typescript

import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideApi } from 'sample-angular-20-0-0-query-param-form-dot';

export const appConfig: ApplicationConfig = {
providers: [
// ...
provideHttpClient(),
provideApi()
],
};
```

**NOTE**
If you're still using `AppModule` and haven't [migrated](https://angular.dev/reference/migrations/standalone) yet, you can still import an Angular module:
```typescript
import { ApiModule } from 'sample-angular-20-0-0-query-param-form-dot';
```

If different from the generated base path, during app bootstrap, you can provide the base path to your service.

```typescript
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideApi } from 'sample-angular-20-0-0-query-param-form-dot';

export const appConfig: ApplicationConfig = {
providers: [
// ...
provideHttpClient(),
provideApi('http://localhost:9999')
],
};
```

```typescript
// with a custom configuration
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideApi } from 'sample-angular-20-0-0-query-param-form-dot';

export const appConfig: ApplicationConfig = {
providers: [
// ...
provideHttpClient(),
provideApi({
withCredentials: true,
username: 'user',
password: 'password'
})
],
};
```

```typescript
// with factory building a custom configuration
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideApi, Configuration } from 'sample-angular-20-0-0-query-param-form-dot';

export const appConfig: ApplicationConfig = {
providers: [
// ...
provideHttpClient(),
{
provide: Configuration,
useFactory: (authService: AuthService) => new Configuration({
basePath: 'http://localhost:9999',
withCredentials: true,
username: authService.getUsername(),
password: authService.getPassword(),
}),
deps: [AuthService],
multi: false
}
],
};
```

### Using multiple OpenAPI files / APIs

In order to use multiple APIs generated from different OpenAPI files,
you can create an alias name when importing the modules
in order to avoid naming conflicts:

```typescript
import { provideApi as provideUserApi } from 'my-user-api-path';
import { provideApi as provideAdminApi } from 'my-admin-api-path';
import { HttpClientModule } from '@angular/common/http';
import { environment } from '../environments/environment';

export const appConfig: ApplicationConfig = {
providers: [
// ...
provideHttpClient(),
provideUserApi(environment.basePath),
provideAdminApi(environment.basePath),
],
};
```

### Customizing path parameter encoding

Without further customization, only [path-parameters][parameter-locations-url] of [style][style-values-url] 'simple'
and Dates for format 'date-time' are encoded correctly.

Other styles (e.g. "matrix") are not that easy to encode
and thus are best delegated to other libraries (e.g.: [@honoluluhenk/http-param-expander]).

To implement your own parameter encoding (or call another library),
pass an arrow-function or method-reference to the `encodeParam` property of the Configuration-object
(see [General Usage](#general-usage) above).

Example value for use in your Configuration-Provider:

```typescript
new Configuration({
encodeParam: (param: Param) => myFancyParamEncoder(param),
})
```

[parameter-locations-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-locations
[style-values-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-values
[@honoluluhenk/http-param-expander]: https://www.npmjs.com/package/@honoluluhenk/http-param-expander

## Form query object dot notation

This client was generated with `useDotNotationForFormObjects=true`. For exploded
form query objects, property names include the parameter name and all parent
properties: `filter = { name: { contains: "Alice" } }` is serialized as
`filter.name.contains=Alice`. Primitive arrays and sets retain repeated keys,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The README advertises nested dot-notation (filter.name.contains=Alice), but the sample spec (query-param-form.yaml) defines Filter with only flat properties, so the generated sample never exercises the recursive nested-object branch of addToHttpParams. Add a nested object property (e.g. name: { contains: string }) to the filter param so the documented behavior and the recursion in the generated code are actually covered by the sample build.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/others/typescript-angular-v20/builds/query-param-form-dot/README.md, line 192:

<comment>The README advertises nested dot-notation (`filter.name.contains=Alice`), but the sample spec (`query-param-form.yaml`) defines `Filter` with only flat properties, so the generated sample never exercises the recursive nested-object branch of `addToHttpParams`. Add a nested object property (e.g. `name: { contains: string }`) to the `filter` param so the documented behavior and the recursion in the generated code are actually covered by the sample build.</comment>

<file context>
@@ -0,0 +1,197 @@
+This client was generated with `useDotNotationForFormObjects=true`. For exploded
+form query objects, property names include the parameter name and all parent
+properties: `filter = { name: { contains: "Alice" } }` is serialized as
+`filter.name.contains=Alice`. Primitive arrays and sets retain repeated keys,
+for example `filter.ids=1&filter.ids=2`.
+
</file context>

for example `filter.ids=1&filter.ids=2`.

This is a server-specific convention, not standard OpenAPI form serialization.
The option defaults to false and does not affect non-exploded form parameters,
JSON, deepObject, spaceDelimited or pipeDelimited parameters.

## Deep-object query parameters

For `style: deepObject`, nested objects are serialized using bracket notation, for example
`filter[name][contains]=Alice`. Arrays and sets use zero-based indices, for example
`filter[sort][0][field]=name`. Dates are serialized as ISO strings; null and undefined
values and empty containers are omitted.

OpenAPI only specifies deepObject serialization for flat objects. This recursive
encoding is a generator extension and requires a server that accepts bracket notation.
Loading
Loading