Skip to content

Conversation

pmoleri
Copy link
Contributor

@pmoleri pmoleri commented Mar 26, 2025

When a TemplateOutlet instance was being destroyed all its locally cached views were not being destroyed.

The parent component (grid) was attempting to clean up all the template outlets on the its destroy. But, this approach is weak because TemplateOutlets could have been destroyed along the way (resize, groupby, etc), so all those caches would have never been destroyed.

The new strategy:

  • uniquely identified views (templateID.id) are meant to be managed by the parent component, so now the parent component destroys them directly and the template outlet never caches them locally.
  • other templates (local) are destroyed by the template outlet destroy.

Example bug scenario in the old code:

  • Grid renders 20 rows
  • Group by is run, some of the template outlets change to summary template, detaching their old row viewref which is kept in the cache.
  • Then, some operation removes templates outlets, e.g. grid resize, page size change, filter data.
  • Those template-outlet instances get destroyed but .destroy() is not called on their cached/detached views. Angular still holds a reference to them permanently.
  • Grid gets destroyed. The remaining template-outlet instances are cleaned up correctly. But previous undestroyed ones are still lingering.

Repro:

  • Go to: https://pmoleri.github.io/grid-leak/grid
  • Don't open the devtools yet
  • Drag "Title" column to group by it
  • Change page size to 5
  • Click on "Show" chekbox to destroy the grid
  • Open Devtools
  • In Memory tab Take a snapshot
  • Select "Object retained by detached DOM nodes"
  • See the leaked <igx-grid-row> elements

Note that this repro is based on 19.1.5 which doesn't contain other fixes. So, you'll find also other leaks.

Additional information (check all that apply):

  • Bug fix
  • New functionality
  • Documentation
  • Demos
  • CI/CD

Checklist:

  • All relevant tags have been applied to this PR
  • This PR includes unit tests covering all the new code (test guidelines)
  • This PR includes API docs for newly added methods/properties (api docs guidelines)
  • This PR includes feature/README.MD updates for the feature docs
  • This PR includes general feature table updates in the root README.MD
  • This PR includes CHANGELOG.MD updates for newly added functionality
  • This PR contains breaking changes
  • This PR includes ng update migrations for the breaking changes (migrations guidelines)
  • This PR includes behavioral changes and the feature specification has been updated with them

When a TemplateOutlet instance was being destroyed all
its locally cached views were not being destroyed.

The parent component (grid) was attempting to clean up all the template outlets
on the its destroy. But, this approach is weak because TemplateOutlets could
have been destroyed along the way (resize, groupby, etc),
so all those caches would have never been destroyed.

The new strategy:
 * uniquely identified views (templateID.id) are meant to be managed by the
   parent component, so now the parent component destroys them directly and the
   template outlet never caches them locally.
 * other templates (local) are destroyed by the template outlet destroy.
@pmoleri pmoleri marked this pull request as ready for review March 27, 2025 12:01
@kdinev kdinev requested review from ChronosSF and kdinev April 15, 2025 12:53
@ChronosSF ChronosSF changed the base branch from master to 20.0.x June 9, 2025 12:03
@github-actions
Copy link

github-actions bot commented Aug 9, 2025

There has been no recent activity and this PR has been marked inactive.

@github-actions github-actions bot added the status: inactive Used to stale issues and pull requests label Aug 9, 2025
@ChronosSF ChronosSF removed the status: inactive Used to stale issues and pull requests label Aug 12, 2025
@ChronosSF ChronosSF changed the base branch from 20.0.x to 20.1.x September 29, 2025 11:23
@ChronosSF ChronosSF requested review from MayaKirova, damyanpetev and igdmdimitrov and removed request for ChronosSF and kdinev September 29, 2025 12:28
@ChronosSF ChronosSF requested a review from Copilot October 18, 2025 07:45
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a memory leak in TemplateOutlet instances where cached views were not being properly destroyed when the template outlets themselves were destroyed. The fix implements a new strategy where uniquely identified views are managed by parent components while local templates are cleaned up by the template outlet's destroy lifecycle.

Key Changes:

  • Implements proper view cleanup in ngOnDestroy for grid components and template outlets
  • Updates caching strategy to differentiate between uniquely identified and local template views
  • Adds templateID context for child row templates to enable proper parent management

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
template_outlet.directive.ts Adds OnDestroy lifecycle and refined caching logic to prevent memory leaks
grid-base.directive.ts Removes centralized template outlet cleanup approach
grid.component.ts Adds OnDestroy implementation with child detail template cleanup
hierarchical-grid.component.ts Adds child grid template cleanup and templateID context
row-island.component.ts Updates grid state cleanup to destroy views directly

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +197 to +199
const hasUniqueId = tmplID.id !== undefined && tmplID.id !== null;
if (hasUniqueId) {
// Don't cache locally unique id views, they're handled by the parent component by using moveview instead of cache
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more explicit check for unique IDs. The current logic treats tmplID.id = 0 or tmplID.id = false as non-unique, which may not be the intended behavior if these are valid ID values.

Suggested change
const hasUniqueId = tmplID.id !== undefined && tmplID.id !== null;
if (hasUniqueId) {
// Don't cache locally unique id views, they're handled by the parent component by using moveview instead of cache
// Don't cache views with a unique ID; they're handled by the parent component using moveView instead of cache
if (tmplID.isUnique === true) {

Copilot uses AI. Check for mistakes.

this._embeddedViewsMap.set(tmplID.type, resCollection);
}

resCollection.set(tmplID.id, viewRef);
Copy link

Copilot AI Oct 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line will always set undefined as the key since tmplID.id is guaranteed to be undefined or null at this point (line 197-201 filters out cases where it has a value). This suggests the caching logic may not work as intended for local templates.

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants