Hello Kendo UI for Angular team,
We found a memory leak in the Kendo UI for Angular DateTimePicker and would like to ask you to fix it. Every time the popup is opened and closed, the complete popup DOM (about 2,000 nodes) and one keydown listener stay in memory until the DateTimePicker itself is destroyed. We could not find an existing report on GitHub, the feedback portal or the forums.
ENVIRONMENT
- @progress/kendo-angular-dateinputs 23.4.0 (our version). The same code is in 24.2.2, in 25.3.0 and in the bundle of your online demo.
- Angular 21.2, RxJS 7.8, Chrome 153, production build.
- Default calendarType ('infinite').
STEPS TO REPRODUCE (on your online demo)
1. Open https://demos.telerik.com/kendo-angular-ui/demos/dateinputs/datetimepicker/basic_usage in Chrome.
2. Open DevTools > Performance monitor (DOM Nodes, JS event listeners), or the Memory tab.
3. Open the popup with the calendar button and close it with Escape. Repeat 8 times.
4. Collect garbage (Memory tab > "Collect garbage") and take a heap snapshot. Filter by "Detached".
ACTUAL RESULT
After forced garbage collection following each cycle:
- DOM nodes: 122 → 2,452 → 4,497 → 6,542 → 8,587 → 10,632 → 12,677 → 14,722 → 16,767 (about +2,045 per open).
- JS event listeners: 63 → 71 (+1 per open).
- The heap snapshot contains 8 detached table.k-calendar-table elements, each with its whole popup subtree, retained by an RxJS subscription teardown.
Closing with Cancel, Set or the toggle button behaves the same. In the same setup, the DatePicker and TimePicker do not grow.
EXPECTED RESULT
After the popup closes and garbage is collected, the DOM node and listener counts return to their previous level.
ROOT CAUSE
For the infinite calendar, DateTimePickerComponent.openPopup() adds a keydown subscription on the month list to the component-lifetime `subscriptions`:
if (this.calendar.type === 'infinite') {
this.subscriptions.add(fromEvent(this.calendar.monthView.list.nativeElement, 'keydown').subscribe((event) => {
...
}));
}
(fesm2022/progress-kendo-angular-dateinputs.mjs: line 16741 in 23.4.0, line 18024 in 25.3.0.)
closePopup() unsubscribes ariaActiveDescendantSubscription, but not this subscription, and `subscriptions` is only unsubscribed in ngOnDestroy(). Every open therefore adds another listener on the new calendar table, and that listener keeps the closed popup alive.
SUGGESTED FIX
Keep the subscription per popup and release it when the popup closes, for example:
private calendarKeydownSubscription: Subscription | null = null;
// openPopup()
if (this.calendar.type === 'infinite') {
this.calendarKeydownSubscription = fromEvent(this.calendar.monthView.list.nativeElement, 'keydown').subscribe((event) => {
...
});
}
// closePopup() and ngOnDestroy()
this.calendarKeydownSubscription?.unsubscribe();
this.calendarKeydownSubscription = null;
IMPACT
We use the DateTimePicker in form fields and in the filter row of a data grid, where the picker lives as long as the grid is shown. Each open adds about 2,000 detached DOM nodes, so memory keeps growing on long-lived screens (100 opens leave about 200,000 detached nodes) until the user leaves the page. Because `subscriptions` is private, we cannot release the subscription cleanly from application code. The subscription is only created for the infinite calendar, but switching to calendarType 'classic' is not an option for us because it changes the calendar experience.
REQUEST
- Please confirm the issue and fix it in an upcoming release, and let us know which version will include the fix.
- If you track it publicly (feedback portal or GitHub), please send us the link so we can follow it.
- Please let us know whether there is a supported workaround until the fix is released.
We are happy to share heap snapshots and our automated Playwright reproduction script.
Kind regards,
Kamil