You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
For Angular HttpClient services and httpResource functions, a query parameter that is both required: true and nullable per the OpenAPI schema is silently dropped from the request whenever its runtime value is null — unless a custom paramsSerializer mutator is configured. This can produce a request the server doesn't recognize as complete.
Reproduction (on current master)
samples/angular-app/src/api/http-client/pets/pets.service.ts (generated from tests/specifications/petstore.yaml's searchPets operation, whose SearchPetsParams declares requirednullableString/requirednullableStringTwo as required + nullable):
filterParams is called with only 2 arguments, so preserveRequiredNullables defaults to false. When params.requirednullableString === null, the key is dropped from filteredParams entirely — the request goes out without it, even though the parameter is documented as required.
Root cause
preserveRequiredNullables is only ever set to true when a paramsSerializer mutator is configured:
packages/core/src/generators/options.ts, generateAxiosOptions — all three isAngular param-building branches (!isRequestOptions IIFE, and the isRequestOptionsangularParamsRef/paramsSerializer/plain branches) gate on !!paramsSerializer or unconditional trueonly inside the paramsSerializer branch; the plain (no-serializer) branch never passes it.
packages/angular/src/http-resource.ts:491 — same gating (preserveRequiredNullables: !!paramsSerializer), so httpResource functions have the identical gap.
This is not a regression — it has been the design since the feature was introduced in 6672367 (feat(angular): preserve required nullable query params). The reason: Angular's own HttpClient/httpResourceparams option type (@angular/common/http, e.g. RequestOptions.params and HttpResourceRequest.params) is:
— it structurally excludes null. Preserving a literal null in the filtered params object is only type-safe when a custom paramsSerializer consumes and converts it before the result reaches Angular's typed params: field; without a serializer, the value has nowhere valid to go.
Note for reviewers: this means the naive fix of adding true as a 3rd argument to the filterParams(...) call in the plain path (as suggested by an automated review comment on #3707) does not actually work — filterParams(..., true) returns Record<string, AngularHttpParamValueWithNullable> (includes null), which is not assignable to Angular's params: field and would fail to compile.
Proposed fix
Don't try to preserve the literal null on the no-serializer path — instead, when a required-nullable key's value is null, emit an empty string ('') instead of dropping the key. This:
needs no type widening ('' is already a valid AngularHttpParamValue, no | null needed, no overload split for this case);
is accepted by Angular's HttpParams natively, encoding as ?key= in the request;
preserves the key's presence (closer to the "required" contract) instead of silently omitting it, without misrepresenting null as some other value.
Concretely, in packages/core/src/generators/options.ts's getAngularFilteredParamsHelperBody() (and the non-shared-helper IIFE twin, getAngularFilteredParamsExpression), add an unconditional branch (no preserveRequiredNullables gate needed, since '' requires no type change):
placed before the existing preserveRequiredNullables-gated null-preserving branch, so:
no paramsSerializer → required-nullable null becomes '' (key preserved, always-on, no config needed);
paramsSerializer configured (preserveRequiredNullables: true) → unchanged, literal null still passed through to the serializer as today, since the serializer explicitly opted into richer null-handling.
This should also thread through packages/angular/src/http-resource.ts for consistency, since it hits the identical gap.
Open question for maintainer input: whether '' is the right universal default, or whether this should be configurable (e.g. an opt-out for users who'd prefer the current drop-silently behavior) — flagging as a design decision rather than presupposing the answer.
Angular filteredParams object support #3326 (closed) — the same shape of bug for a different value category: object-typed query params are dropped by filterParams entirely unless a paramsSerializer/paramsFilter is configured to consume the raw value. Established the precedent this issue follows (serializer-gated passthrough for values HttpParams can't natively represent) — the proposed empty-string fallback here is meant to compose with, not duplicate, that passthrough contract.
fetch client: null query parameter values are serialized as the literal string "null" #3192 (closed) — same root theme (null query param serialization) on the fetch client, with the opposite failure mode: null is serialized as the literal string "null" instead of being dropped. Cross-client evidence that null-valued query params are a recurring gap across generators, worth keeping in mind if a future fix aims for consistent semantics rather than an Angular-only patch.
Feature history for preserveRequiredNullables/requiredNullableParamKeys: 6672367, e2d3c2e, 4acceae (original feature), 7b80844 (#3168, the type-safety fix above), 5440a4c (#3597, the TS6133 fix above).
Surfaced via automated review on #3707 (output.artifacts, unrelated feature — output.artifacts only emits barrels from already-generated files and cannot fix generator-level behavior).
Summary
For Angular
HttpClientservices andhttpResourcefunctions, a query parameter that is bothrequired: trueand nullable per the OpenAPI schema is silently dropped from the request whenever its runtime value isnull— unless a customparamsSerializermutator is configured. This can produce a request the server doesn't recognize as complete.Reproduction (on current master)
samples/angular-app/src/api/http-client/pets/pets.service.ts(generated fromtests/specifications/petstore.yaml'ssearchPetsoperation, whoseSearchPetsParamsdeclaresrequirednullableString/requirednullableStringTwoas required + nullable):filterParamsis called with only 2 arguments, sopreserveRequiredNullablesdefaults tofalse. Whenparams.requirednullableString === null, the key is dropped fromfilteredParamsentirely — the request goes out without it, even though the parameter is documented as required.Root cause
preserveRequiredNullablesis only ever set totruewhen aparamsSerializermutator is configured:packages/core/src/generators/options.ts,generateAxiosOptions— all threeisAngularparam-building branches (!isRequestOptionsIIFE, and theisRequestOptionsangularParamsRef/paramsSerializer/plain branches) gate on!!paramsSerializeror unconditionaltrueonly inside theparamsSerializerbranch; the plain (no-serializer) branch never passes it.packages/angular/src/http-resource.ts:491— same gating (preserveRequiredNullables: !!paramsSerializer), sohttpResourcefunctions have the identical gap.This is not a regression — it has been the design since the feature was introduced in 6672367 (
feat(angular): preserve required nullable query params). The reason: Angular's ownHttpClient/httpResourceparamsoption type (@angular/common/http, e.g.RequestOptions.paramsandHttpResourceRequest.params) is:— it structurally excludes
null. Preserving a literalnullin the filtered params object is only type-safe when a customparamsSerializerconsumes and converts it before the result reaches Angular's typedparams:field; without a serializer, the value has nowhere valid to go.Note for reviewers: this means the naive fix of adding
trueas a 3rd argument to thefilterParams(...)call in the plain path (as suggested by an automated review comment on #3707) does not actually work —filterParams(..., true)returnsRecord<string, AngularHttpParamValueWithNullable>(includesnull), which is not assignable to Angular'sparams:field and would fail to compile.Proposed fix
Don't try to preserve the literal
nullon the no-serializer path — instead, when a required-nullable key's value isnull, emit an empty string ('') instead of dropping the key. This:''is already a validAngularHttpParamValue, no| nullneeded, no overload split for this case);HttpParamsnatively, encoding as?key=in the request;nullas some other value.Concretely, in
packages/core/src/generators/options.ts'sgetAngularFilteredParamsHelperBody()(and the non-shared-helper IIFE twin,getAngularFilteredParamsExpression), add an unconditional branch (nopreserveRequiredNullablesgate needed, since''requires no type change):placed before the existing
preserveRequiredNullables-gated null-preserving branch, so:paramsSerializer→ required-nullablenullbecomes''(key preserved, always-on, no config needed);paramsSerializerconfigured (preserveRequiredNullables: true) → unchanged, literalnullstill passed through to the serializer as today, since the serializer explicitly opted into richer null-handling.This should also thread through
packages/angular/src/http-resource.tsfor consistency, since it hits the identical gap.Open question for maintainer input: whether
''is the right universal default, or whether this should be configurable (e.g. an opt-out for users who'd prefer the current drop-silently behavior) — flagging as a design decision rather than presupposing the answer.Affected areas
packages/core/src/generators/options.ts(getAngularFilteredParamsHelperBody,getAngularFilteredParamsExpression,generateAxiosOptions)packages/angular/src/http-client.ts(consumer)packages/angular/src/http-resource.ts:491(consumer, same gap)Related issues
nullinfilterParams' return type unconditionally. This is whypreserveRequiredNullablesexists as a gated, opt-in flag at all rather than always-on: fix(angular): use null literal in filterParams to fix TypeScript error #3168 introduced the overloadedfilterParamssignature (AngularHttpParamValuevsAngularHttpParamValueWithNullable) specifically to keep the no-serializer path type-safe. This issue is the direct consequence of that constraint — the fix must respect it, not reintroduce filterParams causes typescript error #3104.preserveRequiredNullables === !!paramsSerializerasymmetry from the opposite angle: a TS6133 unused-variable error becauserequiredNullableParamKeyswas emitted even when the branch that reads it was stripped out (i.e. the no-serializer path). Confirms this gating is long-standing, deliberate, and has already surfaced adjacent bugs.filterParamsentirely unless aparamsSerializer/paramsFilteris configured to consume the raw value. Established the precedent this issue follows (serializer-gated passthrough for valuesHttpParamscan't natively represent) — the proposed empty-string fallback here is meant to compose with, not duplicate, that passthrough contract.fetchclient, with the opposite failure mode:nullis serialized as the literal string"null"instead of being dropped. Cross-client evidence thatnull-valued query params are a recurring gap across generators, worth keeping in mind if a future fix aims for consistent semantics rather than an Angular-only patch.Feature history for
preserveRequiredNullables/requiredNullableParamKeys: 6672367, e2d3c2e, 4acceae (original feature), 7b80844 (#3168, the type-safety fix above), 5440a4c (#3597, the TS6133 fix above).Surfaced via automated review on #3707 (
output.artifacts, unrelated feature —output.artifactsonly emits barrels from already-generated files and cannot fix generator-level behavior).