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
-
-
-
-
-
-
+
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
+
+
+
+
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);
}