diff --git a/src/app/date-picker/date-picker.sample.html b/src/app/date-picker/date-picker.sample.html index 789d825aeff..4d58e0c8498 100644 --- a/src/app/date-picker/date-picker.sample.html +++ b/src/app/date-picker/date-picker.sample.html @@ -1,36 +1,37 @@
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..e7b5413497c 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( @@ -155,26 +161,80 @@ export class DatePickerSampleComponent { type: 'text' } } - } + }; - 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; - constructor( - private propertyChangeService: PropertyChangeService, - private destroyRef: DestroyRef - ) { + // FormControl owns the date picker value + public reactiveForm = this.fb.group({ + datePicker: [null], + }); + + constructor() { this.propertyChangeService.setPanelConfig(this.panelConfig); const propertyChange = this.propertyChangeService.propertyChanges.subscribe( (properties) => { this.properties = properties; + this.syncFormControlFromProperties(); } ); this.destroyRef.onDestroy(() => propertyChange.unsubscribe()); } + /** + * 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) { + 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 }); + } + } + protected get modeAngular() { const modeValue = this.propertyChangeService.getProperty('mode'); return modeValue === 'dropdown' @@ -182,7 +242,7 @@ export class DatePickerSampleComponent { : PickerInteractionMode.Dialog; } - protected selectToday(picker) { + protected selectToday(picker: { value: Date; hide: () => void }) { picker.value = new Date(); picker.hide(); } diff --git a/src/app/time-picker/time-picker.sample.html b/src/app/time-picker/time-picker.sample.html index 8cce0135198..73994abe019 100644 --- a/src/app/time-picker/time-picker.sample.html +++ b/src/app/time-picker/time-picker.sample.html @@ -1,4 +1,26 @@
+
+

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..6023e5906f0 100644 --- a/src/app/time-picker/time-picker.sample.ts +++ b/src/app/time-picker/time-picker.sample.ts @@ -1,10 +1,7 @@ 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, - AutoPositionStrategy, - OverlaySettings, DatePart, IgxHintDirective, IgxButtonDirective, @@ -32,6 +29,7 @@ import { imports: [ IgxTimePickerComponent, FormsModule, + ReactiveFormsModule, IgxHintDirective, IgxButtonDirective, IgxPickerActionsDirective, @@ -49,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; @@ -61,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; @@ -72,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: { @@ -105,6 +91,12 @@ export class TimePickerSampleComponent implements OnInit { defaultValue: 'box' } }, + required: { + control: { + type: 'boolean', + defaultValue: false + } + }, readonly: { control: { type: 'boolean', @@ -142,10 +134,22 @@ 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; + + // FormControl owns the time picker value + public reactiveForm = this.fb.group({ + timePicker: [null], + }); + constructor() { this.propertyChangeService.setPanelConfig(this.panelConfig); @@ -153,6 +157,7 @@ export class TimePickerSampleComponent implements OnInit { this.propertyChangeService.propertyChanges.subscribe( (properties) => { this.properties = properties; + this.syncFormControlFromProperties(); } ); @@ -165,11 +170,55 @@ export class TimePickerSampleComponent implements OnInit { ); } - public change() { - this.isRequired = !this.isRequired; - } + /** + * 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) { + 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 }); + } + } - public valueChanged(event) { + // 2) Required validator - set without triggering validation + const currentValidators = control.validator; + const newValidators = this.properties?.required ? Validators.required : null; + + // 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); }