Skip to content

fix(timepicker)!: errors following a full review of the component (#DS-5482) - #1992

Draft
artembelik wants to merge 2 commits into
mainfrom
fix/timepicker-signals
Draft

fix(timepicker)!: errors following a full review of the component (#DS-5482)#1992
artembelik wants to merge 2 commits into
mainfrom
fix/timepicker-signals

Conversation

@artembelik

Copy link
Copy Markdown
Contributor

What

A full review of timepicker, in the same shape as the 20.3.0 component reviews.

What stays a plain accessor, and why

KbqTimepicker implements KbqFormFieldControl, which declares value, id, placeholder, required, disabled, focused, empty and errorState as plain members. Those stay accessors — the interface is how the form field reads them. The six of them carried // TODO: Skipped for migration because: comments from the automated tooling; they say the actual reason now.

min and max reported something other than what was bound

set min(value: D | null) {
    this._min = this.getValidDateOrNull(this.dateAdapter.deserialize(value));
    this.validatorOnChange();
}

A value the date adapter could not read came back as null from the getter. They report what was bound now; the parsed values are internal computeds that still drive the validators, re-run from an effect.

kbqValidationTooltip leaked a subscription per binding

set kbqValidationTooltip(tooltip: KbqTooltipTrigger) {
    
    this.incorrectInput.subscribe(() => {  });   // never unsubscribed
}

Re-binding the input stacked another subscription, and the last one outlived the directive. It is a signal input driven by an effect with an onCleanup teardown.

format's side effects were already covered

Its setter clamped the value, refreshed the placeholder and re-rendered the view. The clamp is the input's transform; the other two are subsumed by the locale effect that already re-read timeFormatPlaceholder — which reads format(), so it re-runs on a format change too.

That effect also stopped returning early on a consumer-provided placeholder. It used to skip the value reformat along with the placeholder update, so a locale change left the rendered time in the old locale whenever the consumer had set their own placeholder.

The existing "Display time corresponding to timeformat" tests — including the unsupported-format fallback and the format-change re-render — pass unchanged, which is the evidence that moving the side effects preserved behavior.

Also

  • The generated id comes from the CDK _IdGenerator instead of a module counter (kbq-timepicker-1kbq-timepicker-a1), and this.id = this.id in the constructor is replaced by initialising the backing field.
  • disabled and required declare booleanAttribute instead of calling coerceBooleanProperty inside their setters.

Migration

timepicker-signals runs from ng update @koobiq/components@20. It rewrites format reads and reports the rest. min and max are deliberately not rewritten: appending () would compile and hand back a different value for anything the adapter could not parse.

There is no template pass — kbqTimepicker is an attribute on a native <input>.

Documented in docs/guides/migration.{en,ru}.md, section 18.

Testing

  • timepicker.spec.ts: 64 → 67 tests. New coverage for the format clamp through the transform, min/max reporting the bound value, and the validators re-running when min changes.
  • timepicker-signals/index.spec.ts: 10 tests.
  • Full packages/components (5164 tests) and packages/schematics (492 tests) suites pass.
  • check-api is in sync.

BREAKING CHANGE

🤖 Generated with Claude Code

`KbqTimepicker` implements `KbqFormFieldControl`, which declares `value`, `id`,
`placeholder`, `required`, `disabled`, `focused`, `empty` and `errorState` as
plain members — those stay plain accessors. The four inputs the timepicker owns
are signals now.

`min` and `max` parsed in their setters and reported the parsed result, so a
value the date adapter could not read came back as `null`. They report what was
bound; the parsed values stay internal, drive the validators and re-run them from
an effect.

`kbqValidationTooltip` was a setter-only input that subscribed to `incorrectInput`
every time it ran and never unsubscribed — re-binding it stacked another
subscription and the last one outlived the directive. It is an effect with a
teardown.

`format` clamps through the input's transform, and the two side effects its
setter carried are subsumed by the locale effect that already re-read the
placeholder. That effect no longer returns early on a consumer-provided
placeholder, so a locale change reformats the rendered time either way.

The generated id comes from the CDK `_IdGenerator`, and the six members that stay
accessors say why instead of carrying stale migration TODOs.

BREAKING CHANGE: `KbqTimepicker.format`, `min`, `max` and `kbqValidationTooltip`
are signal inputs; `min` and `max` report the bound value rather than the parsed
one; generated ids changed shape. Reported and partly rewritten by the
`timepicker-signals` schematic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added bug Something isn't working breaking changes labels Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit 8ba92c0):

https://koobiq-next--prs-1992-as4benoa.web.app

(expires Mon, 07 Sep 2026 07:37:51 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: c9e37e518febda70d0317d07e8ceb35ac43c534c

The validation tooltip teardown had no test, so nothing proved the leak was
fixed. It does now — the test fails against a no-op cleanup and passes against
the real one.

Injections that are never reassigned are `readonly`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking changes bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant