ngx-virtual-dnd 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, signal, computed, effect, Injectable, inject, NgZone, ElementRef, Injector, viewChild, input, output, afterNextRender, ChangeDetectionStrategy, Component, Directive, EnvironmentInjector, TemplateRef, ViewContainerRef } from '@angular/core';
|
|
2
|
+
import { InjectionToken, signal, computed, effect, Injectable, inject, NgZone, ElementRef, Injector, DestroyRef, viewChild, input, output, afterNextRender, ChangeDetectionStrategy, Component, Directive, untracked, EnvironmentInjector, TemplateRef, ViewContainerRef } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
3
4
|
import { NgTemplateOutlet } from '@angular/common';
|
|
5
|
+
import { fromEvent } from 'rxjs';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Initial state for the drag state service.
|
|
@@ -1057,6 +1059,7 @@ class VirtualScrollContainerComponent {
|
|
|
1057
1059
|
#autoScrollService = inject(AutoScrollService);
|
|
1058
1060
|
#ngZone = inject(NgZone);
|
|
1059
1061
|
#injector = inject(Injector);
|
|
1062
|
+
#destroyRef = inject(DestroyRef);
|
|
1060
1063
|
/** ResizeObserver for automatic height detection */
|
|
1061
1064
|
#resizeObserver = null;
|
|
1062
1065
|
/** Measured height from ResizeObserver (used when containerHeight is not provided) */
|
|
@@ -1196,25 +1199,30 @@ class VirtualScrollContainerComponent {
|
|
|
1196
1199
|
draggedItemId = computed(() => {
|
|
1197
1200
|
return this.#dragState.draggedItem()?.draggableId ?? null;
|
|
1198
1201
|
}, ...(ngDevMode ? [{ debugName: "draggedItemId" }] : []));
|
|
1202
|
+
/** Map of item IDs to their indices - rebuilt only when items() changes (O(n) once, then O(1) lookups) */
|
|
1203
|
+
#itemIndexMap = computed(() => {
|
|
1204
|
+
const items = this.items();
|
|
1205
|
+
const idFn = this.itemIdFn();
|
|
1206
|
+
const map = new Map();
|
|
1207
|
+
for (let i = 0; i < items.length; i++) {
|
|
1208
|
+
map.set(idFn(items[i]), i);
|
|
1209
|
+
}
|
|
1210
|
+
return map;
|
|
1211
|
+
}, ...(ngDevMode ? [{ debugName: "#itemIndexMap" }] : []));
|
|
1199
1212
|
/** The index of the currently dragged item in the items array (-1 if not found or not dragging) */
|
|
1200
1213
|
#draggedItemIndex = computed(() => {
|
|
1201
1214
|
const draggedId = this.draggedItemId();
|
|
1202
1215
|
if (!draggedId)
|
|
1203
1216
|
return -1;
|
|
1204
|
-
|
|
1205
|
-
const idFn = this.itemIdFn();
|
|
1206
|
-
for (let i = 0; i < items.length; i++) {
|
|
1207
|
-
if (idFn(items[i]) === draggedId) {
|
|
1208
|
-
return i;
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
return -1;
|
|
1217
|
+
return this.#itemIndexMap().get(draggedId) ?? -1;
|
|
1212
1218
|
}, ...(ngDevMode ? [{ debugName: "#draggedItemIndex" }] : []));
|
|
1219
|
+
/** Memoized Set of sticky IDs - rebuilt only when effectiveStickyIds() changes */
|
|
1220
|
+
#stickyIdsSet = computed(() => new Set(this.effectiveStickyIds()), ...(ngDevMode ? [{ debugName: "#stickyIdsSet" }] : []));
|
|
1213
1221
|
/** Items to render, including sticky items and auto-placeholder */
|
|
1214
1222
|
renderedItems = computed(() => {
|
|
1215
1223
|
const items = this.items();
|
|
1216
1224
|
const { start, end } = this.#renderRange();
|
|
1217
|
-
const stickyIds =
|
|
1225
|
+
const stickyIds = this.#stickyIdsSet();
|
|
1218
1226
|
const idFn = this.itemIdFn();
|
|
1219
1227
|
const draggedId = this.draggedItemId();
|
|
1220
1228
|
// Check if we should insert a placeholder
|
|
@@ -1389,6 +1397,20 @@ class VirtualScrollContainerComponent {
|
|
|
1389
1397
|
}
|
|
1390
1398
|
});
|
|
1391
1399
|
this.#resizeObserver.observe(this.#elementRef.nativeElement);
|
|
1400
|
+
// Set up scroll listener outside Angular zone using RxJS fromEvent
|
|
1401
|
+
// This avoids template event binding which would mark the component dirty 60x/sec
|
|
1402
|
+
fromEvent(this.#elementRef.nativeElement, 'scroll', { passive: true })
|
|
1403
|
+
.pipe(takeUntilDestroyed(this.#destroyRef))
|
|
1404
|
+
.subscribe(() => {
|
|
1405
|
+
const newScrollTop = this.#elementRef.nativeElement.scrollTop;
|
|
1406
|
+
// Only update if the scroll position has changed significantly
|
|
1407
|
+
// (at least 10% of an item height, to reduce updates)
|
|
1408
|
+
const threshold = Math.max(5, this.itemHeight() * 0.1);
|
|
1409
|
+
if (Math.abs(newScrollTop - this.#scrollTop()) >= threshold) {
|
|
1410
|
+
this.#scrollTop.set(newScrollTop);
|
|
1411
|
+
this.scrollPositionChange.emit(newScrollTop);
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1392
1414
|
});
|
|
1393
1415
|
}
|
|
1394
1416
|
ngOnDestroy() {
|
|
@@ -1398,20 +1420,6 @@ class VirtualScrollContainerComponent {
|
|
|
1398
1420
|
const id = this.scrollContainerId() ?? this.#generatedScrollId;
|
|
1399
1421
|
this.#autoScrollService.unregisterContainer(id);
|
|
1400
1422
|
}
|
|
1401
|
-
/**
|
|
1402
|
-
* Handle scroll events.
|
|
1403
|
-
*/
|
|
1404
|
-
onScroll(event) {
|
|
1405
|
-
const target = event.target;
|
|
1406
|
-
const newScrollTop = target.scrollTop;
|
|
1407
|
-
// Only update if the scroll position has changed significantly
|
|
1408
|
-
// (at least 10% of an item height, to reduce updates)
|
|
1409
|
-
const threshold = Math.max(5, this.itemHeight() * 0.1);
|
|
1410
|
-
if (Math.abs(newScrollTop - this.#scrollTop()) >= threshold) {
|
|
1411
|
-
this.#scrollTop.set(newScrollTop);
|
|
1412
|
-
this.scrollPositionChange.emit(newScrollTop);
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
1423
|
/**
|
|
1416
1424
|
* Scroll to a specific position.
|
|
1417
1425
|
*/
|
|
@@ -1446,7 +1454,7 @@ class VirtualScrollContainerComponent {
|
|
|
1446
1454
|
this.scrollTo(newPosition);
|
|
1447
1455
|
}
|
|
1448
1456
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: VirtualScrollContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1449
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: VirtualScrollContainerComponent, isStandalone: true, selector: "vdnd-virtual-scroll", inputs: { itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: true, transformFunction: null }, scrollContainerId: { classPropertyName: "scrollContainerId", publicName: "scrollContainerId", isSignal: true, isRequired: false, transformFunction: null }, autoScrollEnabled: { classPropertyName: "autoScrollEnabled", publicName: "autoScrollEnabled", isSignal: true, isRequired: false, transformFunction: null }, autoScrollConfig: { classPropertyName: "autoScrollConfig", publicName: "autoScrollConfig", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: true, transformFunction: null }, containerHeight: { classPropertyName: "containerHeight", publicName: "containerHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, stickyItemIds: { classPropertyName: "stickyItemIds", publicName: "stickyItemIds", isSignal: true, isRequired: false, transformFunction: null }, itemIdFn: { classPropertyName: "itemIdFn", publicName: "itemIdFn", isSignal: true, isRequired: true, transformFunction: null }, trackByFn: { classPropertyName: "trackByFn", publicName: "trackByFn", isSignal: true, isRequired: false, transformFunction: null }, droppableId: { classPropertyName: "droppableId", publicName: "droppableId", isSignal: true, isRequired: false, transformFunction: null }, autoPlaceholder: { classPropertyName: "autoPlaceholder", publicName: "autoPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, placeholderTemplate: { classPropertyName: "placeholderTemplate", publicName: "placeholderTemplate", isSignal: true, isRequired: false, transformFunction: null }, autoStickyDraggedItem: { classPropertyName: "autoStickyDraggedItem", publicName: "autoStickyDraggedItem", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visibleRangeChange: "visibleRangeChange", scrollPositionChange: "scrollPositionChange" }, host: {
|
|
1457
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: VirtualScrollContainerComponent, isStandalone: true, selector: "vdnd-virtual-scroll", inputs: { itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: true, transformFunction: null }, scrollContainerId: { classPropertyName: "scrollContainerId", publicName: "scrollContainerId", isSignal: true, isRequired: false, transformFunction: null }, autoScrollEnabled: { classPropertyName: "autoScrollEnabled", publicName: "autoScrollEnabled", isSignal: true, isRequired: false, transformFunction: null }, autoScrollConfig: { classPropertyName: "autoScrollConfig", publicName: "autoScrollConfig", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: true, transformFunction: null }, containerHeight: { classPropertyName: "containerHeight", publicName: "containerHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, stickyItemIds: { classPropertyName: "stickyItemIds", publicName: "stickyItemIds", isSignal: true, isRequired: false, transformFunction: null }, itemIdFn: { classPropertyName: "itemIdFn", publicName: "itemIdFn", isSignal: true, isRequired: true, transformFunction: null }, trackByFn: { classPropertyName: "trackByFn", publicName: "trackByFn", isSignal: true, isRequired: false, transformFunction: null }, droppableId: { classPropertyName: "droppableId", publicName: "droppableId", isSignal: true, isRequired: false, transformFunction: null }, autoPlaceholder: { classPropertyName: "autoPlaceholder", publicName: "autoPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, placeholderTemplate: { classPropertyName: "placeholderTemplate", publicName: "placeholderTemplate", isSignal: true, isRequired: false, transformFunction: null }, autoStickyDraggedItem: { classPropertyName: "autoStickyDraggedItem", publicName: "autoStickyDraggedItem", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visibleRangeChange: "visibleRangeChange", scrollPositionChange: "scrollPositionChange" }, host: { properties: { "style.height.px": "containerHeight() ?? null", "style.overflow": "\"auto\"", "style.position": "\"relative\"", "attr.data-item-height": "itemHeight()" }, classAttribute: "vdnd-virtual-scroll" }, viewQueries: [{ propertyName: "contentContainer", first: true, predicate: ["contentContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1450
1458
|
<div class="vdnd-virtual-scroll-content" #contentContainer>
|
|
1451
1459
|
<!-- Single spacer maintains scroll height -->
|
|
1452
1460
|
<div class="vdnd-virtual-scroll-spacer" [style.height.px]="totalHeight()"></div>
|
|
@@ -1482,7 +1490,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
1482
1490
|
'[style.overflow]': '"auto"',
|
|
1483
1491
|
'[style.position]': '"relative"',
|
|
1484
1492
|
'[attr.data-item-height]': 'itemHeight()',
|
|
1485
|
-
'(scroll)': 'onScroll($event)',
|
|
1486
1493
|
}, template: `
|
|
1487
1494
|
<div class="vdnd-virtual-scroll-content" #contentContainer>
|
|
1488
1495
|
<!-- Single spacer maintains scroll height -->
|
|
@@ -1878,8 +1885,8 @@ class DroppableDirective {
|
|
|
1878
1885
|
const active = this.isActive();
|
|
1879
1886
|
const placeholder = this.placeholderId();
|
|
1880
1887
|
const draggedItem = this.#dragState.draggedItem();
|
|
1881
|
-
const cursorPosition = this.#dragState.cursorPosition();
|
|
1882
1888
|
const isDragging = this.#dragState.isDragging();
|
|
1889
|
+
// NOTE: cursorPosition is read with untracked() below to avoid effect running 60x/sec
|
|
1883
1890
|
// Cache state while active for use during drop handling
|
|
1884
1891
|
if (active && isDragging && draggedItem) {
|
|
1885
1892
|
this.#cachedDragState = this.#dragState.getStateSnapshot();
|
|
@@ -1918,13 +1925,18 @@ class DroppableDirective {
|
|
|
1918
1925
|
}
|
|
1919
1926
|
// Handle over (placeholder changed)
|
|
1920
1927
|
if (active && placeholder !== this.#previousPlaceholder) {
|
|
1921
|
-
if (draggedItem
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
+
if (draggedItem) {
|
|
1929
|
+
// Use untracked() to read cursorPosition without tracking it as a dependency
|
|
1930
|
+
// This prevents the effect from running on every cursor move (60Hz during autoscroll)
|
|
1931
|
+
const cursorPosition = untracked(() => this.#dragState.cursorPosition());
|
|
1932
|
+
if (cursorPosition) {
|
|
1933
|
+
this.dragOver.emit({
|
|
1934
|
+
droppableId: this.vdndDroppable(),
|
|
1935
|
+
draggedItem,
|
|
1936
|
+
placeholderId: placeholder,
|
|
1937
|
+
position: cursorPosition,
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1928
1940
|
}
|
|
1929
1941
|
}
|
|
1930
1942
|
this.#wasActive = active;
|
|
@@ -3395,106 +3407,128 @@ class VirtualForDirective {
|
|
|
3395
3407
|
});
|
|
3396
3408
|
}
|
|
3397
3409
|
/**
|
|
3398
|
-
* Update the rendered views.
|
|
3410
|
+
* Update the rendered views with true view recycling.
|
|
3411
|
+
* Views are kept in the DOM and have their context updated in place when possible.
|
|
3399
3412
|
*/
|
|
3400
3413
|
#updateViews() {
|
|
3401
3414
|
const items = this.vdndVirtualForOf();
|
|
3402
3415
|
const { start, end } = this.#renderRange();
|
|
3403
3416
|
const trackByFn = this.vdndVirtualForTrackBy();
|
|
3404
3417
|
const placeholderIndex = this.#placeholderIndex();
|
|
3405
|
-
//
|
|
3406
|
-
|
|
3407
|
-
this.#viewPool.push(view);
|
|
3408
|
-
});
|
|
3409
|
-
this.#activeViews.clear();
|
|
3410
|
-
this.#viewContainer.clear();
|
|
3411
|
-
// Clear wrapper content
|
|
3412
|
-
if (this.#wrapper) {
|
|
3413
|
-
this.#wrapper.innerHTML = '';
|
|
3414
|
-
}
|
|
3415
|
-
const viewsToRender = [];
|
|
3416
|
-
// Render items in range with placeholder
|
|
3418
|
+
// 1. Calculate which keys we need and build the ordered list of items to render
|
|
3419
|
+
const itemsToRender = [];
|
|
3417
3420
|
for (let i = start; i <= end && i < items.length; i++) {
|
|
3418
3421
|
// Insert placeholder before this item if needed
|
|
3419
3422
|
if (placeholderIndex !== null && placeholderIndex === i) {
|
|
3420
|
-
|
|
3423
|
+
itemsToRender.push({
|
|
3424
|
+
key: '__placeholder__',
|
|
3425
|
+
context: {
|
|
3426
|
+
$implicit: { __vdndPlaceholder: true },
|
|
3427
|
+
index: -1,
|
|
3428
|
+
first: false,
|
|
3429
|
+
last: false,
|
|
3430
|
+
count: items.length,
|
|
3431
|
+
isPlaceholder: true,
|
|
3432
|
+
},
|
|
3433
|
+
});
|
|
3434
|
+
}
|
|
3435
|
+
const item = items[i];
|
|
3436
|
+
itemsToRender.push({
|
|
3437
|
+
key: trackByFn(i, item),
|
|
3438
|
+
context: {
|
|
3439
|
+
$implicit: item,
|
|
3440
|
+
index: i,
|
|
3441
|
+
first: i === start,
|
|
3442
|
+
last: i === end || i === items.length - 1,
|
|
3443
|
+
count: items.length,
|
|
3444
|
+
isPlaceholder: false,
|
|
3445
|
+
},
|
|
3446
|
+
});
|
|
3447
|
+
}
|
|
3448
|
+
// Insert placeholder at end if needed
|
|
3449
|
+
if (placeholderIndex !== null && placeholderIndex >= items.length) {
|
|
3450
|
+
itemsToRender.push({
|
|
3451
|
+
key: '__placeholder__',
|
|
3452
|
+
context: {
|
|
3421
3453
|
$implicit: { __vdndPlaceholder: true },
|
|
3422
3454
|
index: -1,
|
|
3423
3455
|
first: false,
|
|
3424
|
-
last:
|
|
3456
|
+
last: true,
|
|
3425
3457
|
count: items.length,
|
|
3426
3458
|
isPlaceholder: true,
|
|
3427
|
-
}
|
|
3428
|
-
|
|
3429
|
-
viewsToRender.push(placeholderView);
|
|
3430
|
-
}
|
|
3431
|
-
const item = items[i];
|
|
3432
|
-
const key = trackByFn(i, item);
|
|
3433
|
-
const context = {
|
|
3434
|
-
$implicit: item,
|
|
3435
|
-
index: i,
|
|
3436
|
-
first: i === start,
|
|
3437
|
-
last: i === end || i === items.length - 1,
|
|
3438
|
-
count: items.length,
|
|
3439
|
-
isPlaceholder: false,
|
|
3440
|
-
};
|
|
3441
|
-
const view = this.#getOrCreateView(key, context);
|
|
3442
|
-
viewsToRender.push(view);
|
|
3459
|
+
},
|
|
3460
|
+
});
|
|
3443
3461
|
}
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
index
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
viewsToRender.push(placeholderView);
|
|
3462
|
+
const neededKeys = new Set(itemsToRender.map((item) => item.key));
|
|
3463
|
+
// 2. Remove views we no longer need (move to pool)
|
|
3464
|
+
for (const [key, view] of this.#activeViews) {
|
|
3465
|
+
if (!neededKeys.has(key)) {
|
|
3466
|
+
const index = this.#viewContainer.indexOf(view);
|
|
3467
|
+
if (index >= 0) {
|
|
3468
|
+
this.#viewContainer.detach(index);
|
|
3469
|
+
}
|
|
3470
|
+
this.#viewPool.push(view);
|
|
3471
|
+
this.#activeViews.delete(key);
|
|
3472
|
+
}
|
|
3456
3473
|
}
|
|
3457
|
-
//
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3474
|
+
// 3. For each needed item, update existing view context or get/create from pool
|
|
3475
|
+
const viewsInOrder = [];
|
|
3476
|
+
for (const { key, context } of itemsToRender) {
|
|
3477
|
+
let view = this.#activeViews.get(key);
|
|
3478
|
+
if (view) {
|
|
3479
|
+
// View exists - update context in place (no DOM manipulation needed)
|
|
3480
|
+
Object.assign(view.context, context);
|
|
3481
|
+
view.markForCheck();
|
|
3482
|
+
}
|
|
3483
|
+
else {
|
|
3484
|
+
// Need a new view - try pool first, then create
|
|
3485
|
+
view = this.#viewPool.pop();
|
|
3486
|
+
if (view) {
|
|
3487
|
+
Object.assign(view.context, context);
|
|
3488
|
+
view.markForCheck();
|
|
3489
|
+
}
|
|
3490
|
+
else {
|
|
3491
|
+
view = this.#templateRef.createEmbeddedView(context);
|
|
3492
|
+
}
|
|
3493
|
+
this.#activeViews.set(key, view);
|
|
3494
|
+
}
|
|
3495
|
+
viewsInOrder.push(view);
|
|
3496
|
+
}
|
|
3497
|
+
// 4. Ensure views are in correct order in ViewContainerRef and wrapper
|
|
3498
|
+
for (let i = 0; i < viewsInOrder.length; i++) {
|
|
3499
|
+
const view = viewsInOrder[i];
|
|
3500
|
+
const currentIndex = this.#viewContainer.indexOf(view);
|
|
3501
|
+
if (currentIndex !== i) {
|
|
3502
|
+
// View needs to be inserted or moved
|
|
3503
|
+
if (currentIndex >= 0) {
|
|
3504
|
+
this.#viewContainer.move(view, i);
|
|
3505
|
+
}
|
|
3506
|
+
else {
|
|
3507
|
+
this.#viewContainer.insert(view, i);
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
// Ensure view's root nodes are in the wrapper (for newly inserted views)
|
|
3461
3511
|
if (this.#wrapper) {
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3512
|
+
const expectedChild = this.#wrapper.children[i];
|
|
3513
|
+
for (const node of view.rootNodes) {
|
|
3514
|
+
if (node instanceof HTMLElement && node !== expectedChild) {
|
|
3515
|
+
// Node is not at expected position, insert it
|
|
3516
|
+
if (expectedChild) {
|
|
3517
|
+
this.#wrapper.insertBefore(node, expectedChild);
|
|
3518
|
+
}
|
|
3519
|
+
else {
|
|
3520
|
+
this.#wrapper.appendChild(node);
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3465
3524
|
}
|
|
3466
|
-
}
|
|
3467
|
-
// Destroy unused views in pool (keep some for reuse)
|
|
3525
|
+
}
|
|
3526
|
+
// 5. Destroy unused views in pool (keep some for reuse)
|
|
3468
3527
|
while (this.#viewPool.length > 10) {
|
|
3469
3528
|
const view = this.#viewPool.pop();
|
|
3470
3529
|
view?.destroy();
|
|
3471
3530
|
}
|
|
3472
3531
|
}
|
|
3473
|
-
/**
|
|
3474
|
-
* Get an existing view from pool or create a new one.
|
|
3475
|
-
*/
|
|
3476
|
-
#getOrCreateView(key, context) {
|
|
3477
|
-
// Check if we have this view active already
|
|
3478
|
-
let view = this.#activeViews.get(key);
|
|
3479
|
-
if (view) {
|
|
3480
|
-
// Update context
|
|
3481
|
-
Object.assign(view.context, context);
|
|
3482
|
-
view.markForCheck();
|
|
3483
|
-
return view;
|
|
3484
|
-
}
|
|
3485
|
-
// Try to reuse from pool
|
|
3486
|
-
view = this.#viewPool.pop();
|
|
3487
|
-
if (view) {
|
|
3488
|
-
Object.assign(view.context, context);
|
|
3489
|
-
view.markForCheck();
|
|
3490
|
-
}
|
|
3491
|
-
else {
|
|
3492
|
-
// Create new view
|
|
3493
|
-
view = this.#templateRef.createEmbeddedView(context);
|
|
3494
|
-
}
|
|
3495
|
-
this.#activeViews.set(key, view);
|
|
3496
|
-
return view;
|
|
3497
|
-
}
|
|
3498
3532
|
/**
|
|
3499
3533
|
* Static method for Angular's structural directive microsyntax.
|
|
3500
3534
|
*/
|