Skip to content
64 changes: 33 additions & 31 deletions src/app/date-picker/date-picker.sample.html
Original file line number Diff line number Diff line change
@@ -1,36 +1,37 @@
<div class="preview" [igSize]="properties.size">
<h6>Angular Date Picker</h6>
<igx-date-picker
#dpAng
[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"
showWeekNumbers="true"
headerOrientation="vertical"
>
<label igxLabel>Select a Date</label>
<igx-suffix (click)="dpAng.open()">
<igx-icon>alarm</igx-icon>
</igx-suffix>
<span igxHint>Helper text</span>
<ng-template igxPickerActions let-calendar>
<button igxButton="flat" (click)="calendar.viewDate = today">
Today
</button>
<button igxButton="flat" (click)="calendar.viewDate = nextYear">
Next Year
</button>
<button igxButton="flat" (click)="dpAng.selectToday()">
Select Today
</button>
</ng-template>
</igx-date-picker>
<form [formGroup]="reactiveForm">
<igx-date-picker
#dpAng
formControlName="datePicker"
[mode]="modeAngular"
[type]="properties.type"
[displayFormat]="properties.displayFormat"
[inputFormat]="properties.inputFormat"
[readOnly]="properties.readonly"
[disabled]="properties.disabled"
[placeholder]="properties.placeholder"
showWeekNumbers="true"
headerOrientation="vertical"
>
<label igxLabel>Select a Date</label>
<igx-suffix (click)="dpAng.open()">
<igx-icon>alarm</igx-icon>
</igx-suffix>
<span igxHint>Helper text</span>
<ng-template igxPickerActions let-calendar>
<button igxButton="flat" (click)="calendar.viewDate = today">
Today
</button>
<button igxButton="flat" (click)="calendar.viewDate = nextYear">
Next Year
</button>
<button igxButton="flat" (click)="dpAng.selectToday()">
Select Today
</button>
</ng-template>
</igx-date-picker>
</form>

<h6>WC Date Picker</h6>
<igc-date-picker
Expand All @@ -41,6 +42,7 @@ <h6>WC Date Picker</h6>
[displayFormat]="properties.displayFormat"
[inputFormat]="properties.inputFormat"
[required]="properties.required"
[readOnly]="properties.readonly"
[disabled]="properties.disabled"
[placeholder]="properties.placeholder"
show-week-numbers="true"
Expand Down
76 changes: 68 additions & 8 deletions src/app/date-picker/date-picker.sample.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -155,34 +161,88 @@ 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'
? PickerInteractionMode.DropDown
: PickerInteractionMode.Dialog;
}

protected selectToday(picker) {
protected selectToday(picker: { value: Date; hide: () => void }) {
picker.value = new Date();
picker.hide();
}
Expand Down
22 changes: 22 additions & 0 deletions src/app/time-picker/time-picker.sample.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
<div class="columns-container">
<article class="timepicker-column">
<h4 class="sample-title">Angular Time Picker with Reactive Form</h4>
<div class="preview" [igSize]="properties.size">
<form [formGroup]="reactiveForm">
<igx-time-picker
#tpReactive
formControlName="timePicker"
[mode]="properties.mode"
[type]="properties.type"
[inputFormat]="properties.inputFormat"
[displayFormat]="properties.displayFormat"
[required]="properties.required"
[readOnly]="properties.readonly"
[disabled]="properties.disabled"
[placeholder]="properties.placeholder"
>
<label igxLabel>Select a Time</label>
<span igxHint>Helper text</span>
</igx-time-picker>
</form>
</div>
</article>
<article class="timepicker-column">
<h4 class="sample-title">Time Picker with Date value binding</h4>
<p class="sample-description">{{showDate(date)}}</p>
Expand Down
91 changes: 70 additions & 21 deletions src/app/time-picker/time-picker.sample.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -32,6 +29,7 @@ import {
imports: [
IgxTimePickerComponent,
FormsModule,
ReactiveFormsModule,
IgxHintDirective,
IgxButtonDirective,
IgxPickerActionsDirective,
Expand All @@ -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<any>;

Expand All @@ -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;

Expand All @@ -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: {
Expand All @@ -105,6 +91,12 @@ export class TimePickerSampleComponent implements OnInit {
defaultValue: 'box'
}
},
required: {
control: {
type: 'boolean',
defaultValue: false
}
},
readonly: {
control: {
type: 'boolean',
Expand Down Expand Up @@ -142,17 +134,30 @@ 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);

const propertyChange =
this.propertyChangeService.propertyChanges.subscribe(
(properties) => {
this.properties = properties;
this.syncFormControlFromProperties();
}
);

Expand All @@ -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);
}

Expand Down
Loading