From f3764315ccbb2c74861320c89bd9aaa5cd3cc65a Mon Sep 17 00:00:00 2001 From: didimmova Date: Tue, 18 Nov 2025 17:31:26 +0200 Subject: [PATCH 1/4] fix(samples): fix datepicker and timepicker samples to use react forms and validators --- src/app/date-picker/date-picker.sample.html | 66 +++++++++++---------- src/app/date-picker/date-picker.sample.ts | 36 +++++++++-- src/app/time-picker/time-picker.sample.html | 23 +++++++ src/app/time-picker/time-picker.sample.ts | 34 ++++++++++- 4 files changed, 120 insertions(+), 39 deletions(-) diff --git a/src/app/date-picker/date-picker.sample.html b/src/app/date-picker/date-picker.sample.html index 789d825aeff..ca220468c6a 100644 --- a/src/app/date-picker/date-picker.sample.html +++ b/src/app/date-picker/date-picker.sample.html @@ -1,36 +1,39 @@
Angular Date Picker
- - - - alarm - - Helper text - - - - - - +
+ + + + alarm + + Helper text + + + + + + +
WC Date Picker
WC Date Picker [displayFormat]="properties.displayFormat" [inputFormat]="properties.inputFormat" [required]="properties.required" + [readOnly]="properties.readonly" [disabled]="properties.disabled" [placeholder]="properties.placeholder" show-week-numbers="true" diff --git a/src/app/date-picker/date-picker.sample.ts b/src/app/date-picker/date-picker.sample.ts index fcc1d9042ed..893af67461d 100644 --- a/src/app/date-picker/date-picker.sample.ts +++ b/src/app/date-picker/date-picker.sample.ts @@ -1,4 +1,5 @@ -import { Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef } from '@angular/core'; +import { Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, inject } from '@angular/core'; +import { ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms'; import { IGX_DATE_PICKER_DIRECTIVES, IgxButtonDirective, @@ -39,9 +40,14 @@ registerIconFromText('alarm', alarm); IgxSuffixDirective, IgxIconComponent, IgSizeDirective, + ReactiveFormsModule, ], }) export class DatePickerSampleComponent { + private fb = inject(UntypedFormBuilder); + private propertyChangeService = inject(PropertyChangeService); + private destroyRef = inject(DestroyRef); + public date1 = new Date(); public date2 = new Date( new Date( @@ -157,24 +163,42 @@ export class DatePickerSampleComponent { } } - public properties: Properties; + public properties: Properties = Object.fromEntries( + Object.keys(this.panelConfig).map((key) => { + const control = this.panelConfig[key]?.control; + return [key, control?.defaultValue]; + }) + ) as Properties; + + public reactiveForm = this.fb.group({ + datePicker: [this.properties?.value || ''], + }); - constructor( - private propertyChangeService: PropertyChangeService, - private destroyRef: DestroyRef - ) { + constructor() { this.propertyChangeService.setPanelConfig(this.panelConfig); const propertyChange = this.propertyChangeService.propertyChanges.subscribe( (properties) => { this.properties = properties; + this.reactiveForm.patchValue({ + datePicker: properties?.value || '' + }); + this.updateRequiredValidator(); } ); this.destroyRef.onDestroy(() => propertyChange.unsubscribe()); } + private updateRequiredValidator(): void { + const control = this.reactiveForm.get('datePicker'); + if (control) { + control.setValidators(this.properties?.required ? Validators.required : null); + control.updateValueAndValidity(); + } + } + protected get modeAngular() { const modeValue = this.propertyChangeService.getProperty('mode'); return modeValue === 'dropdown' diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index 8cce0135198..d138e8654ed 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -1,4 +1,27 @@
+
+

Angular Time Picker with Reactive Form

+
+
+ + + Helper text + +
+
+

Time Picker with Date value binding

{{showDate(date)}}

diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index 04d6673bbb6..fd5e0a0d8d4 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -1,5 +1,5 @@ import { Component, DestroyRef, inject, TemplateRef, ViewChild, OnInit } from '@angular/core'; -import { FormsModule } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms'; import { IgxTimePickerComponent, IgxInputDirective, @@ -32,6 +32,7 @@ import { imports: [ IgxTimePickerComponent, FormsModule, + ReactiveFormsModule, IgxHintDirective, IgxButtonDirective, IgxPickerActionsDirective, @@ -105,6 +106,12 @@ export class TimePickerSampleComponent implements OnInit { defaultValue: 'box' } }, + required: { + control: { + type: 'boolean', + defaultValue: false + } + }, readonly: { control: { type: 'boolean', @@ -142,10 +149,21 @@ export class TimePickerSampleComponent implements OnInit { } } - public properties: Properties; + private fb = inject(UntypedFormBuilder); private propertyChangeService = inject(PropertyChangeService); private destroyRef = inject(DestroyRef); + public properties: Properties = Object.fromEntries( + Object.keys(this.panelConfig).map((key) => { + const control = this.panelConfig[key]?.control; + return [key, control?.defaultValue]; + }) + ) as Properties; + + public reactiveForm = this.fb.group({ + timePicker: [this.properties?.value || ''], + }); + constructor() { this.propertyChangeService.setPanelConfig(this.panelConfig); @@ -153,6 +171,10 @@ export class TimePickerSampleComponent implements OnInit { this.propertyChangeService.propertyChanges.subscribe( (properties) => { this.properties = properties; + this.reactiveForm.patchValue({ + timePicker: properties?.value || '' + }); + this.updateRequiredValidator(); } ); @@ -165,6 +187,14 @@ export class TimePickerSampleComponent implements OnInit { ); } + private updateRequiredValidator(): void { + const control = this.reactiveForm.get('timePicker'); + if (control) { + control.setValidators(this.properties?.required ? Validators.required : null); + control.updateValueAndValidity(); + } + } + public change() { this.isRequired = !this.isRequired; } From d707ab220243ee12376ab3a589786f67f989706c Mon Sep 17 00:00:00 2001 From: desig9stein Date: Wed, 19 Nov 2025 11:04:54 +0200 Subject: [PATCH 2/4] refactor(date-picker): enhance reactive form synchronization with properties panel --- src/app/date-picker/date-picker.sample.ts | 58 ++++++++++++++++++----- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/src/app/date-picker/date-picker.sample.ts b/src/app/date-picker/date-picker.sample.ts index 893af67461d..e7b5413497c 100644 --- a/src/app/date-picker/date-picker.sample.ts +++ b/src/app/date-picker/date-picker.sample.ts @@ -161,7 +161,7 @@ export class DatePickerSampleComponent { type: 'text' } } - } + }; public properties: Properties = Object.fromEntries( Object.keys(this.panelConfig).map((key) => { @@ -170,8 +170,9 @@ export class DatePickerSampleComponent { }) ) as Properties; + // FormControl owns the date picker value public reactiveForm = this.fb.group({ - datePicker: [this.properties?.value || ''], + datePicker: [null], }); constructor() { @@ -181,21 +182,56 @@ export class DatePickerSampleComponent { this.propertyChangeService.propertyChanges.subscribe( (properties) => { this.properties = properties; - this.reactiveForm.patchValue({ - datePicker: properties?.value || '' - }); - this.updateRequiredValidator(); + this.syncFormControlFromProperties(); } ); this.destroyRef.onDestroy(() => propertyChange.unsubscribe()); } - private updateRequiredValidator(): void { + /** + * Syncs the reactive form control with the properties panel: + * - programmatic value updates + * - required validator + * - disabled state + * + * All done in a way that does NOT mark the control dirty/touched. + */ + private syncFormControlFromProperties(): void { const control = this.reactiveForm.get('datePicker'); - if (control) { - control.setValidators(this.properties?.required ? Validators.required : null); - control.updateValueAndValidity(); + if (!control) { + return; + } + + // 1) Programmatic value update (from properties.value) + // This does NOT mark the control dirty/touched. + if ('value' in this.properties) { + const newValue = this.properties.value ?? null; + const currentValue = control.value; + + // Shallow equality check to avoid unnecessary writes + const sameValue = + (newValue === currentValue) || + (newValue instanceof Date && + currentValue instanceof Date && + newValue.getTime() === currentValue.getTime()); + + if (!sameValue) { + control.setValue(newValue, { emitEvent: false }); + } + } + + // 2) Required validator + control.setValidators(this.properties?.required ? Validators.required : null); + // This will trigger statusChanges, but control is still pristine/untouched, + // so IgxDatePicker will keep the visual state INITIAL until user interaction. + control.updateValueAndValidity(); + + // 3) Disabled state + if (this.properties?.disabled) { + control.disable({ emitEvent: false }); + } else { + control.enable({ emitEvent: false }); } } @@ -206,7 +242,7 @@ export class DatePickerSampleComponent { : PickerInteractionMode.Dialog; } - protected selectToday(picker) { + protected selectToday(picker: { value: Date; hide: () => void }) { picker.value = new Date(); picker.hide(); } From cab3ad191b8e6d63392cdfd24c58e238afa8b34a Mon Sep 17 00:00:00 2001 From: desig9stein Date: Wed, 19 Nov 2025 11:07:46 +0200 Subject: [PATCH 3/4] refactor(date-picker): remove unused value and required bindings --- src/app/date-picker/date-picker.sample.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/date-picker/date-picker.sample.html b/src/app/date-picker/date-picker.sample.html index ca220468c6a..4d58e0c8498 100644 --- a/src/app/date-picker/date-picker.sample.html +++ b/src/app/date-picker/date-picker.sample.html @@ -6,10 +6,8 @@
Angular Date Picker
formControlName="datePicker" [mode]="modeAngular" [type]="properties.type" - [value]="properties.value" [displayFormat]="properties.displayFormat" [inputFormat]="properties.inputFormat" - [required]="properties.required" [readOnly]="properties.readonly" [disabled]="properties.disabled" [placeholder]="properties.placeholder" From 492019f0dfd4c2299e184ebdfa3bb857a06430a2 Mon Sep 17 00:00:00 2001 From: didimmova Date: Wed, 19 Nov 2025 14:38:24 +0200 Subject: [PATCH 4/4] sample(timepicker): update timepicker sample --- src/app/time-picker/time-picker.sample.html | 1 - src/app/time-picker/time-picker.sample.ts | 77 +++++++++++++-------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index d138e8654ed..73994abe019 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -8,7 +8,6 @@

Angular Time Picker with Reactive Form

formControlName="timePicker" [mode]="properties.mode" [type]="properties.type" - [value]="properties.value" [inputFormat]="properties.inputFormat" [displayFormat]="properties.displayFormat" [required]="properties.required" diff --git a/src/app/time-picker/time-picker.sample.ts b/src/app/time-picker/time-picker.sample.ts index fd5e0a0d8d4..6023e5906f0 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -2,9 +2,6 @@ import { Component, DestroyRef, inject, TemplateRef, ViewChild, OnInit } from '@ import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms'; import { IgxTimePickerComponent, - IgxInputDirective, - AutoPositionStrategy, - OverlaySettings, DatePart, IgxHintDirective, IgxButtonDirective, @@ -50,9 +47,6 @@ export class TimePickerSampleComponent implements OnInit { @ViewChild('tp', { read: IgxTimePickerComponent, static: true }) public tp: IgxTimePickerComponent; - @ViewChild('target') - public target: IgxInputDirective; - @ViewChild('customControls', { static: true }) public customControlsTemplate!: TemplateRef; @@ -62,7 +56,6 @@ export class TimePickerSampleComponent implements OnInit { public hasHint = false; public itemsDelta = { hours: 1, minutes: 15, seconds: 20 }; - public format = 'hh:mm:ss:SS a'; public spinLoop = true; public datePart = DatePart.Hours; @@ -73,14 +66,6 @@ export class TimePickerSampleComponent implements OnInit { public val = '08:30:00'; public today = new Date(Date.now()); - public isRequired = true; - - public myOverlaySettings: OverlaySettings = { - modal: false, - closeOnOutsideClick: true, - positionStrategy: new AutoPositionStrategy() - }; - public panelConfig: PropertyPanelConfig = { size: { control: { @@ -160,8 +145,9 @@ export class TimePickerSampleComponent implements OnInit { }) ) as Properties; + // FormControl owns the time picker value public reactiveForm = this.fb.group({ - timePicker: [this.properties?.value || ''], + timePicker: [null], }); constructor() { @@ -171,10 +157,7 @@ export class TimePickerSampleComponent implements OnInit { this.propertyChangeService.propertyChanges.subscribe( (properties) => { this.properties = properties; - this.reactiveForm.patchValue({ - timePicker: properties?.value || '' - }); - this.updateRequiredValidator(); + this.syncFormControlFromProperties(); } ); @@ -187,19 +170,55 @@ export class TimePickerSampleComponent implements OnInit { ); } - private updateRequiredValidator(): void { + /** + * Syncs the reactive form control with the properties panel: + * - programmatic value updates + * - required validator + * - disabled state + * + * All done in a way that does NOT mark the control dirty/touched. + */ + private syncFormControlFromProperties(): void { const control = this.reactiveForm.get('timePicker'); - if (control) { - control.setValidators(this.properties?.required ? Validators.required : null); - control.updateValueAndValidity(); + if (!control) { + return; } - } - public change() { - this.isRequired = !this.isRequired; - } + // 1) Programmatic value update (from properties.value) + // This does NOT mark the control dirty/touched. + if ('value' in this.properties) { + const newValue = this.properties.value ?? null; + const currentValue = control.value; + + // Shallow equality check to avoid unnecessary writes + const sameValue = + (newValue === currentValue) || + (newValue instanceof Date && + currentValue instanceof Date && + newValue.getTime() === currentValue.getTime()); + + if (!sameValue) { + control.setValue(newValue, { emitEvent: false }); + } + } + + // 2) Required validator - set without triggering validation + const currentValidators = control.validator; + const newValidators = this.properties?.required ? Validators.required : null; - public valueChanged(event) { + // Only update validators if they actually changed + if (currentValidators !== newValidators) { + control.setValidators(newValidators); + // Don't call updateValueAndValidity - let natural form lifecycle handle validation + } + + // 3) Disabled state + if (this.properties?.disabled) { + control.disable({ emitEvent: false }); + } else { + control.enable({ emitEvent: false }); + } + } public valueChanged(event) { console.log(event); }