Skip to content

Commit 91bed5d

Browse files
authored
fix(samples): fix datepicker and timepicker samples to use reactive forms and validators (#16487)
1 parent 82eee1c commit 91bed5d

File tree

4 files changed

+193
-60
lines changed

4 files changed

+193
-60
lines changed

src/app/date-picker/date-picker.sample.html

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,37 @@
11
<div class="preview" [igSize]="properties.size">
22
<h6>Angular Date Picker</h6>
3-
<igx-date-picker
4-
#dpAng
5-
[mode]="modeAngular"
6-
[type]="properties.type"
7-
[value]="properties.value"
8-
[displayFormat]="properties.displayFormat"
9-
[inputFormat]="properties.inputFormat"
10-
[required]="properties.required"
11-
[readOnly]="properties.readonly"
12-
[disabled]="properties.disabled"
13-
[placeholder]="properties.placeholder"
14-
showWeekNumbers="true"
15-
headerOrientation="vertical"
16-
>
17-
<label igxLabel>Select a Date</label>
18-
<igx-suffix (click)="dpAng.open()">
19-
<igx-icon>alarm</igx-icon>
20-
</igx-suffix>
21-
<span igxHint>Helper text</span>
22-
<ng-template igxPickerActions let-calendar>
23-
<button igxButton="flat" (click)="calendar.viewDate = today">
24-
Today
25-
</button>
26-
<button igxButton="flat" (click)="calendar.viewDate = nextYear">
27-
Next Year
28-
</button>
29-
<button igxButton="flat" (click)="dpAng.selectToday()">
30-
Select Today
31-
</button>
32-
</ng-template>
33-
</igx-date-picker>
3+
<form [formGroup]="reactiveForm">
4+
<igx-date-picker
5+
#dpAng
6+
formControlName="datePicker"
7+
[mode]="modeAngular"
8+
[type]="properties.type"
9+
[displayFormat]="properties.displayFormat"
10+
[inputFormat]="properties.inputFormat"
11+
[readOnly]="properties.readonly"
12+
[disabled]="properties.disabled"
13+
[placeholder]="properties.placeholder"
14+
showWeekNumbers="true"
15+
headerOrientation="vertical"
16+
>
17+
<label igxLabel>Select a Date</label>
18+
<igx-suffix (click)="dpAng.open()">
19+
<igx-icon>alarm</igx-icon>
20+
</igx-suffix>
21+
<span igxHint>Helper text</span>
22+
<ng-template igxPickerActions let-calendar>
23+
<button igxButton="flat" (click)="calendar.viewDate = today">
24+
Today
25+
</button>
26+
<button igxButton="flat" (click)="calendar.viewDate = nextYear">
27+
Next Year
28+
</button>
29+
<button igxButton="flat" (click)="dpAng.selectToday()">
30+
Select Today
31+
</button>
32+
</ng-template>
33+
</igx-date-picker>
34+
</form>
3435

3536
<h6>WC Date Picker</h6>
3637
<igc-date-picker
@@ -41,6 +42,7 @@ <h6>WC Date Picker</h6>
4142
[displayFormat]="properties.displayFormat"
4243
[inputFormat]="properties.inputFormat"
4344
[required]="properties.required"
45+
[readOnly]="properties.readonly"
4446
[disabled]="properties.disabled"
4547
[placeholder]="properties.placeholder"
4648
show-week-numbers="true"

src/app/date-picker/date-picker.sample.ts

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef } from '@angular/core';
1+
import { Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, inject } from '@angular/core';
2+
import { ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms';
23
import {
34
IGX_DATE_PICKER_DIRECTIVES,
45
IgxButtonDirective,
@@ -39,9 +40,14 @@ registerIconFromText('alarm', alarm);
3940
IgxSuffixDirective,
4041
IgxIconComponent,
4142
IgSizeDirective,
43+
ReactiveFormsModule,
4244
],
4345
})
4446
export class DatePickerSampleComponent {
47+
private fb = inject(UntypedFormBuilder);
48+
private propertyChangeService = inject(PropertyChangeService);
49+
private destroyRef = inject(DestroyRef);
50+
4551
public date1 = new Date();
4652
public date2 = new Date(
4753
new Date(
@@ -155,34 +161,88 @@ export class DatePickerSampleComponent {
155161
type: 'text'
156162
}
157163
}
158-
}
164+
};
159165

160-
public properties: Properties;
166+
public properties: Properties = Object.fromEntries(
167+
Object.keys(this.panelConfig).map((key) => {
168+
const control = this.panelConfig[key]?.control;
169+
return [key, control?.defaultValue];
170+
})
171+
) as Properties;
161172

162-
constructor(
163-
private propertyChangeService: PropertyChangeService,
164-
private destroyRef: DestroyRef
165-
) {
173+
// FormControl owns the date picker value
174+
public reactiveForm = this.fb.group({
175+
datePicker: [null],
176+
});
177+
178+
constructor() {
166179
this.propertyChangeService.setPanelConfig(this.panelConfig);
167180

168181
const propertyChange =
169182
this.propertyChangeService.propertyChanges.subscribe(
170183
(properties) => {
171184
this.properties = properties;
185+
this.syncFormControlFromProperties();
172186
}
173187
);
174188

175189
this.destroyRef.onDestroy(() => propertyChange.unsubscribe());
176190
}
177191

192+
/**
193+
* Syncs the reactive form control with the properties panel:
194+
* - programmatic value updates
195+
* - required validator
196+
* - disabled state
197+
*
198+
* All done in a way that does NOT mark the control dirty/touched.
199+
*/
200+
private syncFormControlFromProperties(): void {
201+
const control = this.reactiveForm.get('datePicker');
202+
if (!control) {
203+
return;
204+
}
205+
206+
// 1) Programmatic value update (from properties.value)
207+
// This does NOT mark the control dirty/touched.
208+
if ('value' in this.properties) {
209+
const newValue = this.properties.value ?? null;
210+
const currentValue = control.value;
211+
212+
// Shallow equality check to avoid unnecessary writes
213+
const sameValue =
214+
(newValue === currentValue) ||
215+
(newValue instanceof Date &&
216+
currentValue instanceof Date &&
217+
newValue.getTime() === currentValue.getTime());
218+
219+
if (!sameValue) {
220+
control.setValue(newValue, { emitEvent: false });
221+
}
222+
}
223+
224+
// 2) Required validator
225+
control.setValidators(this.properties?.required ? Validators.required : null);
226+
// This will trigger statusChanges, but control is still pristine/untouched,
227+
// so IgxDatePicker will keep the visual state INITIAL until user interaction.
228+
control.updateValueAndValidity();
229+
230+
// 3) Disabled state
231+
if (this.properties?.disabled) {
232+
control.disable({ emitEvent: false });
233+
} else {
234+
control.enable({ emitEvent: false });
235+
}
236+
}
237+
178238
protected get modeAngular() {
179239
const modeValue = this.propertyChangeService.getProperty('mode');
180240
return modeValue === 'dropdown'
181241
? PickerInteractionMode.DropDown
182242
: PickerInteractionMode.Dialog;
183243
}
184244

185-
protected selectToday(picker) {
245+
protected selectToday(picker: { value: Date; hide: () => void }) {
186246
picker.value = new Date();
187247
picker.hide();
188248
}

src/app/time-picker/time-picker.sample.html

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,26 @@
11
<div class="columns-container">
2+
<article class="timepicker-column">
3+
<h4 class="sample-title">Angular Time Picker with Reactive Form</h4>
4+
<div class="preview" [igSize]="properties.size">
5+
<form [formGroup]="reactiveForm">
6+
<igx-time-picker
7+
#tpReactive
8+
formControlName="timePicker"
9+
[mode]="properties.mode"
10+
[type]="properties.type"
11+
[inputFormat]="properties.inputFormat"
12+
[displayFormat]="properties.displayFormat"
13+
[required]="properties.required"
14+
[readOnly]="properties.readonly"
15+
[disabled]="properties.disabled"
16+
[placeholder]="properties.placeholder"
17+
>
18+
<label igxLabel>Select a Time</label>
19+
<span igxHint>Helper text</span>
20+
</igx-time-picker>
21+
</form>
22+
</div>
23+
</article>
224
<article class="timepicker-column">
325
<h4 class="sample-title">Time Picker with Date value binding</h4>
426
<p class="sample-description">{{showDate(date)}}</p>

src/app/time-picker/time-picker.sample.ts

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { Component, DestroyRef, inject, TemplateRef, ViewChild, OnInit } from '@angular/core';
2-
import { FormsModule } from '@angular/forms';
2+
import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, Validators } from '@angular/forms';
33
import {
44
IgxTimePickerComponent,
5-
IgxInputDirective,
6-
AutoPositionStrategy,
7-
OverlaySettings,
85
DatePart,
96
IgxHintDirective,
107
IgxButtonDirective,
@@ -32,6 +29,7 @@ import {
3229
imports: [
3330
IgxTimePickerComponent,
3431
FormsModule,
32+
ReactiveFormsModule,
3533
IgxHintDirective,
3634
IgxButtonDirective,
3735
IgxPickerActionsDirective,
@@ -49,9 +47,6 @@ export class TimePickerSampleComponent implements OnInit {
4947
@ViewChild('tp', { read: IgxTimePickerComponent, static: true })
5048
public tp: IgxTimePickerComponent;
5149

52-
@ViewChild('target')
53-
public target: IgxInputDirective;
54-
5550
@ViewChild('customControls', { static: true })
5651
public customControlsTemplate!: TemplateRef<any>;
5752

@@ -61,7 +56,6 @@ export class TimePickerSampleComponent implements OnInit {
6156
public hasHint = false;
6257

6358
public itemsDelta = { hours: 1, minutes: 15, seconds: 20 };
64-
public format = 'hh:mm:ss:SS a';
6559
public spinLoop = true;
6660
public datePart = DatePart.Hours;
6761

@@ -72,14 +66,6 @@ export class TimePickerSampleComponent implements OnInit {
7266
public val = '08:30:00';
7367
public today = new Date(Date.now());
7468

75-
public isRequired = true;
76-
77-
public myOverlaySettings: OverlaySettings = {
78-
modal: false,
79-
closeOnOutsideClick: true,
80-
positionStrategy: new AutoPositionStrategy()
81-
};
82-
8369
public panelConfig: PropertyPanelConfig = {
8470
size: {
8571
control: {
@@ -105,6 +91,12 @@ export class TimePickerSampleComponent implements OnInit {
10591
defaultValue: 'box'
10692
}
10793
},
94+
required: {
95+
control: {
96+
type: 'boolean',
97+
defaultValue: false
98+
}
99+
},
108100
readonly: {
109101
control: {
110102
type: 'boolean',
@@ -142,17 +134,30 @@ export class TimePickerSampleComponent implements OnInit {
142134
}
143135
}
144136

145-
public properties: Properties;
137+
private fb = inject(UntypedFormBuilder);
146138
private propertyChangeService = inject(PropertyChangeService);
147139
private destroyRef = inject(DestroyRef);
148140

141+
public properties: Properties = Object.fromEntries(
142+
Object.keys(this.panelConfig).map((key) => {
143+
const control = this.panelConfig[key]?.control;
144+
return [key, control?.defaultValue];
145+
})
146+
) as Properties;
147+
148+
// FormControl owns the time picker value
149+
public reactiveForm = this.fb.group({
150+
timePicker: [null],
151+
});
152+
149153
constructor() {
150154
this.propertyChangeService.setPanelConfig(this.panelConfig);
151155

152156
const propertyChange =
153157
this.propertyChangeService.propertyChanges.subscribe(
154158
(properties) => {
155159
this.properties = properties;
160+
this.syncFormControlFromProperties();
156161
}
157162
);
158163

@@ -165,11 +170,55 @@ export class TimePickerSampleComponent implements OnInit {
165170
);
166171
}
167172

168-
public change() {
169-
this.isRequired = !this.isRequired;
170-
}
173+
/**
174+
* Syncs the reactive form control with the properties panel:
175+
* - programmatic value updates
176+
* - required validator
177+
* - disabled state
178+
*
179+
* All done in a way that does NOT mark the control dirty/touched.
180+
*/
181+
private syncFormControlFromProperties(): void {
182+
const control = this.reactiveForm.get('timePicker');
183+
if (!control) {
184+
return;
185+
}
186+
187+
// 1) Programmatic value update (from properties.value)
188+
// This does NOT mark the control dirty/touched.
189+
if ('value' in this.properties) {
190+
const newValue = this.properties.value ?? null;
191+
const currentValue = control.value;
192+
193+
// Shallow equality check to avoid unnecessary writes
194+
const sameValue =
195+
(newValue === currentValue) ||
196+
(newValue instanceof Date &&
197+
currentValue instanceof Date &&
198+
newValue.getTime() === currentValue.getTime());
199+
200+
if (!sameValue) {
201+
control.setValue(newValue, { emitEvent: false });
202+
}
203+
}
171204

172-
public valueChanged(event) {
205+
// 2) Required validator - set without triggering validation
206+
const currentValidators = control.validator;
207+
const newValidators = this.properties?.required ? Validators.required : null;
208+
209+
// Only update validators if they actually changed
210+
if (currentValidators !== newValidators) {
211+
control.setValidators(newValidators);
212+
// Don't call updateValueAndValidity - let natural form lifecycle handle validation
213+
}
214+
215+
// 3) Disabled state
216+
if (this.properties?.disabled) {
217+
control.disable({ emitEvent: false });
218+
} else {
219+
control.enable({ emitEvent: false });
220+
}
221+
} public valueChanged(event) {
173222
console.log(event);
174223
}
175224

0 commit comments

Comments
 (0)