• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 24424912916

14 Apr 2026 09:56PM UTC coverage: 90.143% (-0.03%) from 90.174%
24424912916

Pull #17160

github

web-flow
Merge 7b44604fb into 34c791054
Pull Request #17160: Do not use outlet internally

14832 of 17276 branches covered (85.85%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 2 files covered. (100.0%)

11 existing lines in 6 files now uncovered.

29879 of 32324 relevant lines covered (92.44%)

34650.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.45
/projects/igniteui-angular/date-picker/src/date-picker/date-picker.component.ts
1
import {
2
    AfterViewChecked,
3
    AfterViewInit,
4
    AfterContentChecked,
5
    ChangeDetectorRef,
6
    Component,
7
    ContentChild,
8
    EventEmitter,
9
    HostBinding,
10
    HostListener,
11
    Injector,
12
    Input,
13
    OnDestroy,
14
    OnInit,
15
    Output,
16
    PipeTransform,
17
    Renderer2,
18
    ViewChild,
19
    ViewContainerRef,
20
    booleanAttribute,
21
    inject
22
} from '@angular/core';
23
import {
24
    AbstractControl,
25
    ControlValueAccessor,
26
    NgControl,
27
    NG_VALIDATORS,
28
    NG_VALUE_ACCESSOR,
29
    ValidationErrors,
30
    Validator
31
} from '@angular/forms';
32
import {
33
    IgxCalendarComponent, IgxCalendarHeaderTemplateDirective, IgxCalendarHeaderTitleTemplateDirective, IgxCalendarSubheaderTemplateDirective,
34
     IFormattingViews, IFormattingOptions
35
} from 'igniteui-angular/calendar';
36
import {
37
    IgxLabelDirective, IgxInputState, IgxInputGroupComponent, IgxPrefixDirective, IgxInputDirective, IgxSuffixDirective,
38
    IgxReadOnlyInputDirective
39
} from 'igniteui-angular/input-group';
40
import { fromEvent, Subscription, noop, MonoTypeOperatorFunction } from 'rxjs';
41
import { filter, takeUntil } from 'rxjs/operators';
42

43
import { IgxDateTimeEditorDirective, IgxTextSelectionDirective } from 'igniteui-angular/directives';
44
import {
45
    AbsoluteScrollStrategy,
46
    AutoPositionStrategy,
47
    IgxOverlayService,
48
    OverlayCancelableEventArgs,
49
    OverlayEventArgs,
50
    OverlaySettings,
51
    IgxPickerActionsDirective,
52
    DatePickerResourceStringsEN,
53
    IDatePickerResourceStrings,
54
    DateRangeDescriptor,
55
    DateRangeType,
56
    IBaseCancelableBrowserEventArgs,
57
    isDate,
58
    PlatformUtil,
59
    getCurrentResourceStrings,
60
    PickerCalendarOrientation,
61
    DateTimeUtil,
62
    DatePartDeltas,
63
    DatePart,
64
    isDateInRanges,
65
    IgxOverlayOutletDirective,
66
    I18N_FORMATTER
67
} from 'igniteui-angular/core';
68
import { IDatePickerValidationFailedEventArgs } from './date-picker.common';
69
import { IgxIconComponent } from 'igniteui-angular/icon';
70
import { fadeIn, fadeOut } from 'igniteui-angular/animations';
71
import { PickerBaseDirective } from './picker-base.directive';
72
import { IgxCalendarContainerComponent } from './calendar-container/calendar-container.component';
73

74
let NEXT_ID = 0;
3✔
75

76
/**
77
 * Date Picker displays a popup calendar that lets users select a single date.
78
 *
79
 * @igxModule IgxDatePickerModule
80
 * @igxTheme igx-calendar-theme, igx-icon-theme
81
 * @igxGroup Scheduling
82
 * @igxKeywords datepicker, calendar, schedule, date
83
 * @example
84
 * ```html
85
 * <igx-date-picker [(ngModel)]="selectedDate"></igx-date-picker>
86
 * ```
87
 */
88
@Component({
89
    providers: [
90
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxDatePickerComponent, multi: true },
91
        { provide: NG_VALIDATORS, useExisting: IgxDatePickerComponent, multi: true }
92
    ],
93
    selector: 'igx-date-picker',
94
    templateUrl: 'date-picker.component.html',
95
    styles: [':host { display: block; }'],
96
    imports: [
97
        IgxInputGroupComponent,
98
        IgxPrefixDirective,
99
        IgxIconComponent,
100
        IgxInputDirective,
101
        IgxReadOnlyInputDirective,
102
        IgxDateTimeEditorDirective,
103
        IgxTextSelectionDirective,
104
        IgxSuffixDirective
105
    ]
106
})
107
export class IgxDatePickerComponent extends PickerBaseDirective implements ControlValueAccessor, Validator,
3✔
108
    OnInit, AfterViewInit, OnDestroy, AfterViewChecked, AfterContentChecked {
109
    private _overlayService = inject<IgxOverlayService>(IgxOverlayService);
127✔
110
    private _injector = inject(Injector);
127✔
111
    private _renderer = inject(Renderer2);
127✔
112
    private platform = inject(PlatformUtil);
127✔
113
    private cdr = inject(ChangeDetectorRef);
127✔
114
    private _i18nFormatter = inject(I18N_FORMATTER);
127✔
115

116

117
    /**
118
     * Gets/Sets whether the inactive dates will be hidden.
119
     *
120
     * @remarks
121
     * Applies to dates that are out of the current month.
122
     * Default value is `false`.
123
     * @example
124
     * ```html
125
     * <igx-date-picker [hideOutsideDays]="true"></igx-date-picker>
126
     * ```
127
     * @example
128
     * ```typescript
129
     * let hideOutsideDays = this.datePicker.hideOutsideDays;
130
     * ```
131
     */
132
    @Input({ transform: booleanAttribute })
133
    public hideOutsideDays: boolean;
134

135
    /**
136
     * Gets/Sets the number of month views displayed.
137
     *
138
     * @remarks
139
     * Default value is `1`.
140
     *
141
     * @example
142
     * ```html
143
     * <igx-date-picker [displayMonthsCount]="2"></igx-date-picker>
144
     * ```
145
     * @example
146
     * ```typescript
147
     * let monthViewsDisplayed = this.datePicker.displayMonthsCount;
148
     * ```
149
     */
150
    @Input()
151
    public displayMonthsCount = 1;
127✔
152

153
    /**
154
    * Gets/Sets the orientation of the multiple months displayed in the picker's calendar's days view.
155
    *
156
    * @example
157
    * <igx-date-picker orientation="vertical"></igx-date-picker>
158
    */
159
    @Input()
160
    public orientation: PickerCalendarOrientation = PickerCalendarOrientation.Horizontal;
127✔
161

162
    /**
163
     * Show/hide week numbers
164
     *
165
     * @example
166
     * ```html
167
     * <igx-date-picker [showWeekNumbers]="true"></igx-date-picker>
168
     * ``
169
     */
170
    @Input({ transform: booleanAttribute })
171
    public showWeekNumbers: boolean;
172

173

174
    /**
175
     * Gets/Sets the date which is shown in the calendar picker and is highlighted.
176
     * By default it is the current date, or the value of the picker, if set.
177
     */
178
    @Input()
179
    public get activeDate(): Date {
180
        const today = new Date(new Date().setHours(0, 0, 0, 0));
212✔
181
        const dateValue = DateTimeUtil.isValidDate(this._dateValue) ? new Date(this._dateValue.setHours(0, 0, 0, 0)) : null;
212✔
182
        return this._activeDate ?? dateValue ?? this._calendar?.activeDate ?? today;
212✔
183
    }
184

185
    public set activeDate(value: Date) {
186
        this._activeDate = value;
1✔
187
    }
188

189
    /**
190
     * Gets/Sets a custom formatter function on the selected or passed date.
191
     *
192
     * @example
193
     * ```html
194
     * <igx-date-picker [value]="date" [formatter]="formatter"></igx-date-picker>
195
     * ```
196
     */
197
    @Input()
198
    public formatter: (val: Date) => string;
199

200
    /**
201
     * Gets/Sets the today button's label.
202
     *
203
     *  @example
204
     * ```html
205
     * <igx-date-picker todayButtonLabel="Today"></igx-date-picker>
206
     * ```
207
     */
208
    @Input()
209
    public todayButtonLabel: string;
210

211
    /**
212
     * Gets/Sets the cancel button's label.
213
     *
214
     * @example
215
     * ```html
216
     * <igx-date-picker cancelButtonLabel="Cancel"></igx-date-picker>
217
     * ```
218
     */
219
    @Input()
220
    public cancelButtonLabel: string;
221

222
    /**
223
     * Specify if the currently spun date segment should loop over.
224
     *
225
     *  @example
226
     * ```html
227
     * <igx-date-picker [spinLoop]="false"></igx-date-picker>
228
     * ```
229
     */
230
    @Input({ transform: booleanAttribute })
231
    public spinLoop = true;
127✔
232

233
    /**
234
     * Delta values used to increment or decrement each editor date part on spin actions.
235
     * All values default to `1`.
236
     *
237
     * @example
238
     * ```html
239
     * <igx-date-picker [spinDelta]="{ date: 5, month: 2 }"></igx-date-picker>
240
     * ```
241
     */
242
    @Input()
243
    public spinDelta: Pick<DatePartDeltas, 'date' | 'month' | 'year'>;
244

245
    /**
246
     * Gets/Sets the value of `id` attribute.
247
     *
248
     * @remarks If not provided it will be automatically generated.
249
     * @example
250
     * ```html
251
     * <igx-date-picker [id]="'igx-date-picker-3'" cancelButtonLabel="cancel" todayButtonLabel="today"></igx-date-picker>
252
     * ```
253
     */
254
    @Input()
255
    @HostBinding('attr.id')
256
    public id = `igx-date-picker-${NEXT_ID++}`;
127✔
257

258
    //#region calendar members
259

260
    /**
261
     * Gets/Sets the format views of the `IgxDatePickerComponent`.
262
     *
263
     * @example
264
     * ```typescript
265
     * let formatViews = this.datePicker.formatViews;
266
     *  this.datePicker.formatViews = {day:false, month: false, year:false};
267
     * ```
268
     */
269
    @Input()
270
    public formatViews: IFormattingViews;
271

272
    /**
273
     * Gets/Sets the disabled dates descriptors.
274
     *
275
     * @example
276
     * ```typescript
277
     * let disabledDates = this.datepicker.disabledDates;
278
     * this.datePicker.disabledDates = [ {type: DateRangeType.Weekends}, ...];
279
     * ```
280
     */
281
    @Input()
282
    public get disabledDates(): DateRangeDescriptor[] {
283
        return this._disabledDates;
129✔
284
    }
285
    public set disabledDates(value: DateRangeDescriptor[]) {
286
        this._disabledDates = value;
3✔
287
        this._onValidatorChange();
3✔
288
    }
289

290
    /**
291
     * Gets/Sets the special dates descriptors.
292
     *
293
     * @example
294
     * ```typescript
295
     * let specialDates = this.datepicker.specialDates;
296
     * this.datePicker.specialDates = [ {type: DateRangeType.Weekends}, ... ];
297
     * ```
298
     */
299
    @Input()
300
    public get specialDates(): DateRangeDescriptor[] {
301
        return this._specialDates;
55✔
302
    }
303
    public set specialDates(value: DateRangeDescriptor[]) {
304
        this._specialDates = value;
1✔
305
    }
306

307

308
    /**
309
     * Gets/Sets the format options of the `IgxDatePickerComponent`.
310
     *
311
     * @example
312
     * ```typescript
313
     * this.datePicker.calendarFormat = {day: "numeric",  month: "long", weekday: "long", year: "numeric"};
314
     * ```
315
     */
316
    @Input()
317
    public calendarFormat: IFormattingOptions;
318

319
    //#endregion
320

321
    /**
322
     * Gets/Sets the selected date.
323
     *
324
     *  @example
325
     * ```html
326
     * <igx-date-picker [value]="date"></igx-date-picker>
327
     * ```
328
     */
329
    @Input()
330
    public get value(): Date | string {
331
        return this._value;
841✔
332
    }
333
    public set value(date: Date | string) {
334
        this._value = date;
147✔
335
        this.setDateValue(date);
147✔
336
        if (this.dateTimeEditor.value !== date) {
147✔
337
            this.dateTimeEditor.value = this._dateValue;
110✔
338
        }
339
        this.valueChange.emit(this.dateValue);
147✔
340
        this._onChangeCallback(this.dateValue);
147✔
341
    }
342

343
    /**
344
     * The minimum value the picker will accept.
345
     *
346
     * @example
347
     * <igx-date-picker [minValue]="minDate"></igx-date-picker>
348
     */
349
    @Input()
350
    public set minValue(value: Date | string) {
351
        this._minValue = value;
30✔
352
        this._onValidatorChange();
30✔
353
    }
354

355
    public get minValue(): Date | string {
356
        return this._minValue;
905✔
357
    }
358

359
    /**
360
     * The maximum value the picker will accept.
361
     *
362
     * @example
363
     * <igx-date-picker [maxValue]="maxDate"></igx-date-picker>
364
     */
365
    @Input()
366
    public set maxValue(value: Date | string) {
367
        this._maxValue = value;
33✔
368
        this._onValidatorChange();
33✔
369
    }
370

371
    public get maxValue(): Date | string {
372
        return this._maxValue;
905✔
373
    }
374

375
    /**
376
     * Gets/Sets the resource strings for the picker's default toggle icon.
377
     * By default it uses EN resources.
378
     */
379
    @Input()
380
    public resourceStrings: IDatePickerResourceStrings;
381

382
    /** @hidden @internal */
383
    @Input({ transform: booleanAttribute })
384
    public readOnly = false;
127✔
385

386
    /**
387
     * Emitted when the picker's value changes.
388
     *
389
     * @remarks
390
     * Used for `two-way` bindings.
391
     *
392
     * @example
393
     * ```html
394
     * <igx-date-picker [(value)]="date"></igx-date-picker>
395
     * ```
396
     */
397
    @Output()
398
    public valueChange = new EventEmitter<Date>();
127✔
399

400
    /**
401
     * Emitted when the user types/spins invalid date in the date-picker editor.
402
     *
403
     *  @example
404
     * ```html
405
     * <igx-date-picker (validationFailed)="onValidationFailed($event)"></igx-date-picker>
406
     * ```
407
     */
408
    @Output()
409
    public validationFailed = new EventEmitter<IDatePickerValidationFailedEventArgs>();
127✔
410

411
    /** @hidden @internal */
412
    @ContentChild(IgxLabelDirective)
413
    public label: IgxLabelDirective;
414

415
    @ContentChild(IgxCalendarHeaderTitleTemplateDirective)
416
    private headerTitleTemplate: IgxCalendarHeaderTitleTemplateDirective;
417

418
    @ContentChild(IgxCalendarHeaderTemplateDirective)
419
    private headerTemplate: IgxCalendarHeaderTemplateDirective;
420

421
    @ViewChild(IgxDateTimeEditorDirective, { static: true })
422
    private dateTimeEditor: IgxDateTimeEditorDirective;
423

424
    @ViewChild(IgxInputGroupComponent, { read: ViewContainerRef })
425
    private viewContainerRef: ViewContainerRef;
426

427
    @ViewChild(IgxLabelDirective)
428
    private labelDirective: IgxLabelDirective;
429

430
    @ViewChild(IgxInputDirective)
431
    private inputDirective: IgxInputDirective;
432

433
    @ContentChild(IgxCalendarSubheaderTemplateDirective)
434
    private subheaderTemplate: IgxCalendarSubheaderTemplateDirective;
435

436
    @ContentChild(IgxPickerActionsDirective)
437
    private pickerActions: IgxPickerActionsDirective;
438

439
    private get dialogOverlaySettings(): OverlaySettings {
440
        return Object.assign({}, this._dialogOverlaySettings, this.overlaySettings);
6✔
441
    }
442

443
    private get dropDownOverlaySettings(): OverlaySettings {
444
        return Object.assign({}, this._dropDownOverlaySettings, this.overlaySettings);
47✔
445
    }
446

447
    private get inputGroupElement(): HTMLElement {
448
        return this.inputGroup?.element.nativeElement.querySelector('.igx-input-group__bundle');
221✔
449
    }
450

451
    private get dateValue(): Date {
452
        return this._dateValue;
386✔
453
    }
454

455
    private get pickerFormatViews(): IFormattingViews {
456
        return Object.assign({}, this._defFormatViews, this.formatViews);
58✔
457
    }
458

459
    private get pickerCalendarFormat(): IFormattingOptions {
460
        return Object.assign({}, this._calendarFormat, this.calendarFormat);
55✔
461
    }
462

463
    /** @hidden @internal */
464
    public displayValue: PipeTransform = { transform: (date: Date) => this.formatter(date) };
127✔
465

466
    private _resourceStrings = getCurrentResourceStrings(DatePickerResourceStringsEN);
127✔
467
    private _dateValue: Date;
468
    private _overlayId: string;
469
    private _value: Date | string;
470
    private _ngControl: NgControl = null;
127✔
471
    private _statusChanges$: Subscription;
472
    private _calendar: IgxCalendarComponent;
473
    private _calendarContainer?: HTMLElement;
474
    private _specialDates: DateRangeDescriptor[] = null;
127✔
475
    private _disabledDates: DateRangeDescriptor[] = null;
127✔
476
    private _activeDate: Date = null;
127✔
477
    private _overlaySubFilter:
127✔
478
        [MonoTypeOperatorFunction<OverlayEventArgs>,
479
            MonoTypeOperatorFunction<OverlayEventArgs | OverlayCancelableEventArgs>] = [
480
            filter(x => x.id === this._overlayId),
352✔
481
            takeUntil(this._destroy$)
482
        ];
483
    private _dropDownOverlaySettings: OverlaySettings = {
127✔
484
        target: this.inputGroupElement,
485
        closeOnOutsideClick: true,
486
        modal: false,
487
        closeOnEscape: true,
488
        scrollStrategy: new AbsoluteScrollStrategy(),
489
        positionStrategy: new AutoPositionStrategy({
490
            openAnimation: fadeIn,
491
            closeAnimation: fadeOut
492
        })
493
    };
494
    private _dialogOverlaySettings: OverlaySettings = {
127✔
495
        closeOnOutsideClick: true,
496
        modal: true,
497
        closeOnEscape: true
498
    };
499
    private _calendarFormat: IFormattingOptions = {
127✔
500
        day: 'numeric',
501
        month: 'short',
502
        weekday: 'short',
503
        year: 'numeric'
504
    };
505
    private _defFormatViews: IFormattingViews = {
127✔
506
        day: false,
507
        month: true,
508
        year: false
509
    };
510
    private _onChangeCallback: (_: Date) => void = noop;
127✔
511
    private _onTouchedCallback: () => void = noop;
127✔
512
    private _onValidatorChange: () => void = noop;
127✔
513

514
    constructor() {
515
        super();
127✔
516
        this.initLocale();
127✔
517
    }
518

519
    /** @hidden @internal */
520
    public get required(): boolean {
521
        if (this._ngControl && this._ngControl.control && this._ngControl.control.validator) {
142✔
522
            // Run the validation with empty object to check if required is enabled.
523
            const error = this._ngControl.control.validator({} as AbstractControl);
136✔
524
            return error && error.required;
136✔
525
        }
526

527
        return false;
6✔
528
    }
529

530
    /** @hidden @internal */
531
    public get pickerResourceStrings(): IDatePickerResourceStrings {
532
        return Object.assign({}, this._resourceStrings, this.resourceStrings);
410✔
533
    }
534

535
    protected override get toggleContainer(): HTMLElement | undefined {
UNCOV
536
        return this._calendarContainer;
×
537
    }
538

539
    /** @hidden @internal */
540
    @HostListener('keydown', ['$event'])
541
    public onKeyDown(event: KeyboardEvent) {
542
        switch (event.key) {
3!
543
            case this.platform.KEYMAP.ARROW_UP:
544
                if (event.altKey) {
×
545
                    this.close();
×
546
                }
547
                break;
×
548
            case this.platform.KEYMAP.ARROW_DOWN:
549
                if (event.altKey) {
1✔
550
                    this.open();
1✔
551
                }
552
                break;
1✔
553
            case this.platform.KEYMAP.SPACE:
554
                event.preventDefault();
1✔
555
                this.open();
1✔
556
                break;
1✔
557
        }
558
    }
559

560
    /**
561
     * Opens the picker's dropdown or dialog.
562
     *
563
     * @example
564
     * ```html
565
     * <igx-date-picker #picker></igx-date-picker>
566
     *
567
     * <button type="button" igxButton (click)="picker.open()">Open Dialog</button>
568
     * ```
569
     */
570
    public open(settings?: OverlaySettings): void {
571
        if (!this.collapsed || this.disabled || this.readOnly) {
67✔
572
            return;
14✔
573
        }
574

575
        const overlaySettings = Object.assign({}, this.isDropdown
53✔
576
            ? this.dropDownOverlaySettings
577
            : this.dialogOverlaySettings
578
            , settings);
579

580
        if (this.isDropdown && this.inputGroupElement) {
53✔
581
            overlaySettings.target = this.inputGroupElement;
47✔
582
        }
583
        if (this.outlet) {
53!
UNCOV
584
            overlaySettings.outlet = this.outlet;
×
585
        }
586
        this._overlayId = this._overlayService
53✔
587
            .attach(IgxCalendarContainerComponent, this.viewContainerRef, overlaySettings);
588
        this._overlayService.show(this._overlayId);
53✔
589
    }
590

591
    /**
592
     * Toggles the picker's dropdown or dialog
593
     *
594
     * @example
595
     * ```html
596
     * <igx-date-picker #picker></igx-date-picker>
597
     *
598
     * <button type="button" igxButton (click)="picker.toggle()">Toggle Dialog</button>
599
     * ```
600
     */
601
    public toggle(settings?: OverlaySettings): void {
602
        if (this.collapsed) {
9✔
603
            this.open(settings);
8✔
604
        } else {
605
            this.close();
1✔
606
        }
607
    }
608

609
    /**
610
     * Closes the picker's dropdown or dialog.
611
     *
612
     * @example
613
     * ```html
614
     * <igx-date-picker #picker></igx-date-picker>
615
     *
616
     * <button type="button" igxButton (click)="picker.close()">Close Dialog</button>
617
     * ```
618
     */
619
    public close(): void {
620
        if (!this.collapsed) {
24✔
621
            this._overlayService.hide(this._overlayId);
23✔
622
        }
623
    }
624

625
    /**
626
     * Selects a date.
627
     *
628
     * @remarks Updates the value in the input field.
629
     *
630
     * @example
631
     * ```typescript
632
     * this.datePicker.select(date);
633
     * ```
634
     * @param date passed date that has to be set to the calendar.
635
     */
636
    public select(value: Date): void {
637
        this.value = value;
3✔
638
    }
639

640
    /**
641
     * Selects today's date and closes the picker.
642
     *
643
     * @example
644
     * ```html
645
     * <igx-date-picker #picker></igx-date-picker>
646
     *
647
     * <button type="button" igxButton (click)="picker.selectToday()">Select Today</button>
648
     * ```
649
     * */
650
    public selectToday(): void {
651
        const today = new Date();
1✔
652
        today.setHours(0);
1✔
653
        today.setMinutes(0);
1✔
654
        today.setSeconds(0);
1✔
655
        today.setMilliseconds(0);
1✔
656
        this.select(today);
1✔
657
        this.close();
1✔
658
    }
659

660
    /**
661
     * Clears the input field and the picker's value.
662
     *
663
     * @example
664
     * ```typescript
665
     * this.datePicker.clear();
666
     * ```
667
     */
668
    public clear(): void {
669
        if (!this.disabled || !this.readOnly) {
7!
670
            this._calendar?.deselectDate();
7✔
671
            this.dateTimeEditor.clear();
7✔
672
        }
673
    }
674

675
    /**
676
     * Increment a specified `DatePart`.
677
     *
678
     * @param datePart The optional DatePart to increment. Defaults to Date.
679
     * @param delta The optional delta to increment by. Overrides `spinDelta`.
680
     * @example
681
     * ```typescript
682
     * this.datePicker.increment(DatePart.Date);
683
     * ```
684
     */
685
    public increment(datePart?: DatePart, delta?: number): void {
686
        this.dateTimeEditor.increment(datePart, delta);
4✔
687
    }
688

689
    /**
690
     * Decrement a specified `DatePart`
691
     *
692
     * @param datePart The optional DatePart to decrement. Defaults to Date.
693
     * @param delta The optional delta to decrement by. Overrides `spinDelta`.
694
     * @example
695
     * ```typescript
696
     * this.datePicker.decrement(DatePart.Date);
697
     * ```
698
     */
699
    public decrement(datePart?: DatePart, delta?: number): void {
700
        this.dateTimeEditor.decrement(datePart, delta);
4✔
701
    }
702

703
    //#region Control Value Accessor
704
    /** @hidden @internal */
705
    public writeValue(value: Date | string) {
706
        this._value = value;
112✔
707
        this.setDateValue(value);
112✔
708
        if (this.dateTimeEditor.value !== value) {
112✔
709
            this.dateTimeEditor.value = this._dateValue;
54✔
710
        }
711
    }
712

713
    /** @hidden @internal */
714
    public registerOnChange(fn: any) {
715
        this._onChangeCallback = fn;
92✔
716
    }
717

718
    /** @hidden @internal */
719
    public registerOnTouched(fn: any) {
720
        this._onTouchedCallback = fn;
92✔
721
    }
722

723
    /** @hidden @internal */
724
    public setDisabledState?(isDisabled: boolean): void {
725
        this.disabled = isDisabled;
67✔
726
    }
727
    //#endregion
728

729
    //#region Validator
730
    /** @hidden @internal */
731
    public registerOnValidatorChange(fn: any) {
732
        this._onValidatorChange = fn;
92✔
733
    }
734

735
    /** @hidden @internal */
736
    public validate(control: AbstractControl): ValidationErrors | null {
737
        if (!control.value) {
268✔
738
            return null;
197✔
739
        }
740
        // InvalidDate handling
741
        if (isDate(control.value) && !DateTimeUtil.isValidDate(control.value)) {
71!
742
            return { value: true };
×
743
        }
744

745
        const errors = {};
71✔
746
        const value = DateTimeUtil.isValidDate(control.value) ? control.value : DateTimeUtil.parseIsoDate(control.value);
71!
747
        if (value && this.disabledDates && isDateInRanges(value, this.disabledDates)) {
71!
748
            Object.assign(errors, { dateIsDisabled: true });
×
749
        }
750
        Object.assign(errors, DateTimeUtil.validateMinMax(value, this.minValue, this.maxValue, false));
71✔
751

752
        return Object.keys(errors).length > 0 ? errors : null;
71!
753
    }
754
    //#endregion
755

756
    /** @hidden @internal */
757
    public ngOnInit(): void {
758
        this._ngControl = this._injector.get<NgControl>(NgControl, null);
112✔
759
    }
760

761
    /** @hidden @internal */
762
    public override ngAfterViewInit() {
763
        super.ngAfterViewInit();
122✔
764
        this.subscribeToClick();
122✔
765
        this.subscribeToOverlayEvents();
122✔
766
        this.subscribeToDateEditorEvents();
122✔
767

768
        this._dropDownOverlaySettings.excludeFromOutsideClick = [this.inputGroup.element.nativeElement];
122✔
769

770
        fromEvent(this.inputDirective.nativeElement, 'blur')
122✔
771
            .pipe(takeUntil(this._destroy$))
772
            .subscribe(() => {
773
                if (this.collapsed) {
45✔
774
                    this._onTouchedCallback();
19✔
775
                    this.updateValidity();
19✔
776
                }
777
            });
778

779
        if (this._ngControl) {
122✔
780
            this._statusChanges$ =
65✔
781
                this._ngControl.statusChanges.subscribe(this.onStatusChanged.bind(this));
782
            if (this._ngControl.control.validator) {
65✔
783
                this.inputGroup.isRequired = this.required;
64✔
784
                this.cdr.detectChanges();
64✔
785
            }
786
        }
787
    }
788

789
    /** @hidden @internal */
790
    public ngAfterViewChecked() {
791
        if (this.labelDirective) {
383✔
792
            this._renderer.setAttribute(this.inputDirective.nativeElement, 'aria-labelledby', this.labelDirective.id);
1✔
793
        }
794
    }
795

796
    /** @hidden @internal */
797
    public override ngOnDestroy(): void {
798
        super.ngOnDestroy();
128✔
799
        if (this._statusChanges$) {
128✔
800
            this._statusChanges$.unsubscribe();
66✔
801
        }
802
        if (this._overlayId) {
128✔
803
            this._overlayService.detach(this._overlayId);
6✔
804
            delete this._overlayId;
6✔
805
        }
806
    }
807

808
    /** @hidden @internal */
809
    public getEditElement(): HTMLInputElement {
810
        return this.inputDirective.nativeElement;
219✔
811
    }
812

813
    private subscribeToClick() {
814
        fromEvent(this.getEditElement(), 'click')
122✔
815
            .pipe(takeUntil(this._destroy$))
816
            .subscribe(() => {
817
                if (!this.isDropdown) {
15✔
818
                    this.toggle();
1✔
819
                }
820
            });
821
    }
822

823
    private setDateValue(value: Date | string) {
824
        if (isDate(value) && isNaN(value.getTime())) {
259✔
825
            this._dateValue = value;
1✔
826
            return;
1✔
827
        }
828
        this._dateValue = DateTimeUtil.isValidDate(value) ? value : DateTimeUtil.parseIsoDate(value);
258✔
829
        if (this._calendar) {
258✔
830
            this._calendar.selectDate(this._dateValue);
38✔
831
            this._calendar.activeDate = this.activeDate;
38✔
832
            this._calendar.viewDate = this.activeDate;
38✔
833
            this.cdr.detectChanges();
38✔
834
        }
835
    }
836

837
    private updateValidity() {
838
        // B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526
839
        if (this._ngControl && !this.disabled && this.isTouchedOrDirty) {
97✔
840
            if (this.hasValidators && this.inputGroup.isFocused) {
47✔
841
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
17✔
842
            } else {
843
                this.inputDirective.valid = this._ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
30✔
844
            }
845
        } else {
846
            this.inputDirective.valid = IgxInputState.INITIAL;
50✔
847
        }
848
    }
849

850
    private get isTouchedOrDirty(): boolean {
851
        return (this._ngControl.control.touched || this._ngControl.control.dirty);
82✔
852
    }
853

854
    private get hasValidators(): boolean {
855
        return (!!this._ngControl.control.validator || !!this._ngControl.control.asyncValidator);
47✔
856
    }
857

858
    private onStatusChanged = () => {
127✔
859
        this.disabled = this._ngControl.disabled;
77✔
860
        this.updateValidity();
77✔
861
        this.inputGroup.isRequired = this.required;
77✔
862
    };
863

864
    private handleSelection(date: Date): void {
865
        if (this.dateValue && DateTimeUtil.isValidDate(this.dateValue)) {
13✔
866
            date.setHours(this.dateValue.getHours());
1✔
867
            date.setMinutes(this.dateValue.getMinutes());
1✔
868
            date.setSeconds(this.dateValue.getSeconds());
1✔
869
            date.setMilliseconds(this.dateValue.getMilliseconds());
1✔
870
        }
871
        this.value = date;
13✔
872
        if (this._calendar) {
13✔
873
            this._calendar.activeDate = this.activeDate;
13✔
874
            this._calendar.viewDate = this.activeDate;
13✔
875
        }
876
        this.close();
13✔
877
    }
878

879
    private subscribeToDateEditorEvents(): void {
880
        this.dateTimeEditor.valueChange.pipe(
122✔
881
            takeUntil(this._destroy$)).subscribe(val => {
882
                this.value = val;
32✔
883
            });
884
        this.dateTimeEditor.validationFailed.pipe(
122✔
885
            takeUntil(this._destroy$)).subscribe((event) => {
886
                this.validationFailed.emit({
1✔
887
                    owner: this,
888
                    prevValue: event.oldValue,
889
                    currentValue: this.value
890
                });
891
            });
892
    }
893

894
    private subscribeToOverlayEvents() {
895
        this._overlayService.opening.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
122✔
896
            const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
53✔
897
            this.opening.emit(args);
53✔
898
            e.cancel = args.cancel;
53✔
899
            if (args.cancel) {
53✔
900
                this._overlayService.detach(this._overlayId);
1✔
901
                return;
1✔
902
            }
903

904
            this._initializeCalendarContainer(e.componentRef.instance);
52✔
905
            this._calendarContainer = e.componentRef.location.nativeElement;
52✔
906
            this._collapsed = false;
52✔
907
        });
908

909
        this._overlayService.opened.pipe(...this._overlaySubFilter).subscribe(() => {
122✔
910
            this.opened.emit({ owner: this });
45✔
911

912
            this._calendar.wrapper?.nativeElement?.focus();
45✔
913
        });
914

915
        this._overlayService.closing.pipe(...this._overlaySubFilter).subscribe((e: OverlayCancelableEventArgs) => {
122✔
916
            const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
24✔
917
            this.closing.emit(args);
24✔
918
            e.cancel = args.cancel;
24✔
919
            if (args.cancel) {
24✔
920
                return;
1✔
921
            }
922
            // do not focus the input if clicking outside in dropdown mode
923
            const outsideEvent = args.event && (args.event as KeyboardEvent).key !== this.platform.KEYMAP.ESCAPE;
23✔
924
            if (this.getEditElement() && !(outsideEvent && this.isDropdown)) {
23✔
925
                this.inputDirective.focus();
22✔
926
            } else {
927
                this._onTouchedCallback();
1✔
928
                this.updateValidity();
1✔
929
            }
930
        });
931

932
        this._overlayService.closed.pipe(...this._overlaySubFilter).subscribe(() => {
122✔
933
            this.closed.emit({ owner: this });
24✔
934
            this._overlayService.detach(this._overlayId);
24✔
935
            this._collapsed = true;
24✔
936
            this._overlayId = null;
24✔
937
            this._calendar = null;
24✔
938
            this._calendarContainer = undefined;
24✔
939
        });
940
    }
941

942
    private getMinMaxDates() {
943
        const minValue = DateTimeUtil.isValidDate(this.minValue) ? this.minValue : DateTimeUtil.parseIsoDate(this.minValue);
52✔
944
        const maxValue = DateTimeUtil.isValidDate(this.maxValue) ? this.maxValue : DateTimeUtil.parseIsoDate(this.maxValue);
52✔
945
        return { minValue, maxValue };
52✔
946
    }
947

948
    private setDisabledDates(): void {
949
        this._calendar.disabledDates = this.disabledDates ? [...this.disabledDates] : [];
52✔
950
        const { minValue, maxValue } = this.getMinMaxDates();
52✔
951
        if (minValue) {
52✔
952
            this._calendar.disabledDates.push({ type: DateRangeType.Before, dateRange: [minValue] });
4✔
953
        }
954
        if (maxValue) {
52✔
955
            this._calendar.disabledDates.push({ type: DateRangeType.After, dateRange: [maxValue] });
5✔
956
        }
957
    }
958

959
    private _initializeCalendarContainer(componentInstance: IgxCalendarContainerComponent) {
960
        this._calendar = componentInstance.calendar;
52✔
961
        this._calendar.hasHeader = !this.isDropdown && !this.hideHeader;
52✔
962
        this._calendar.formatOptions = this.pickerCalendarFormat;
52✔
963
        this._calendar.formatViews = this.pickerFormatViews;
52✔
964
        this._calendar.locale = this.locale;
52✔
965
        this._calendar.weekStart = this.weekStart;
52✔
966
        this._calendar.specialDates = this.specialDates;
52✔
967
        this._calendar.headerTitleTemplate = this.headerTitleTemplate;
52✔
968
        this._calendar.headerTemplate = this.headerTemplate;
52✔
969
        this._calendar.subheaderTemplate = this.subheaderTemplate;
52✔
970
        this._calendar.headerOrientation = this.headerOrientation;
52✔
971
        this._calendar.hideOutsideDays = this.hideOutsideDays;
52✔
972
        this._calendar.monthsViewNumber = this.displayMonthsCount;
52✔
973
        this._calendar.showWeekNumbers = this.showWeekNumbers;
52✔
974
        this._calendar.orientation = this.orientation;
52✔
975
        this._calendar.selected.pipe(takeUntil(this._destroy$)).subscribe((ev: Date) => this.handleSelection(ev));
52✔
976
        this.setDisabledDates();
52✔
977

978
        if (DateTimeUtil.isValidDate(this.dateValue)) {
52✔
979
            // calendar will throw if the picker's value is InvalidDate #9208
980
            this._calendar.value = this.dateValue;
21✔
981
        }
982
        this._calendar.activeDate = this.activeDate;
52✔
983
        this._calendar.viewDate = this.activeDate;
52✔
984

985
        componentInstance.mode = this.mode;
52✔
986
        componentInstance.closeButtonLabel = this.cancelButtonLabel;
52✔
987
        componentInstance.todayButtonLabel = this.todayButtonLabel;
52✔
988
        componentInstance.pickerActions = this.pickerActions;
52✔
989

990
        componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close());
52✔
991
        componentInstance.todaySelection.pipe(takeUntil(this._destroy$)).subscribe(() => this.selectToday());
52✔
992
    }
993

994
    protected override updateResources(): void {
995
        this._resourceStrings = getCurrentResourceStrings(DatePickerResourceStringsEN, false, this._locale);
305✔
996
    }
997
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc