refactor(ivy): move static parts of LView.cleanup to TView (#24301)
PR Close #24301
This commit is contained in:

committed by
Victor Berchet

parent
8db928df9d
commit
86b13ccf80
@ -154,30 +154,14 @@ let data: any[];
|
||||
*/
|
||||
let directives: any[]|null;
|
||||
|
||||
/**
|
||||
* When a view is destroyed, listeners need to be released and outputs need to be
|
||||
* unsubscribed. This cleanup array stores both listener data (in chunks of 4)
|
||||
* and output data (in chunks of 2) for a particular view. Combining the arrays
|
||||
* saves on memory (70 bytes per array) and on a few bytes of code size (for two
|
||||
* separate for loops).
|
||||
*
|
||||
* If it's a listener being stored:
|
||||
* 1st index is: event name to remove
|
||||
* 2nd index is: native element
|
||||
* 3rd index is: listener function
|
||||
* 4th index is: useCapture boolean
|
||||
*
|
||||
* If it's an output subscription:
|
||||
* 1st index is: unsubscribe function
|
||||
* 2nd index is: context for function
|
||||
*/
|
||||
let cleanup: any[]|null;
|
||||
|
||||
export function getCleanup(): any[] {
|
||||
function getCleanup(view: LView): any[] {
|
||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||
return cleanup || (cleanup = currentView.cleanup = []);
|
||||
return view.cleanupInstances || (view.cleanupInstances = []);
|
||||
}
|
||||
|
||||
function getTViewCleanup(view: LView): any[] {
|
||||
return view.tView.cleanup || (view.tView.cleanup = []);
|
||||
}
|
||||
/**
|
||||
* In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error.
|
||||
*
|
||||
@ -213,7 +197,6 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
|
||||
creationMode = newView && (newView.flags & LViewFlags.CreationMode) === LViewFlags.CreationMode;
|
||||
firstTemplatePass = newView && newView.tView.firstTemplatePass;
|
||||
|
||||
cleanup = newView && newView.cleanup;
|
||||
renderer = newView && newView.renderer;
|
||||
|
||||
if (host != null) {
|
||||
@ -312,7 +295,7 @@ export function createLView<T>(
|
||||
data: [],
|
||||
directives: null,
|
||||
tView: tView,
|
||||
cleanup: null,
|
||||
cleanupInstances: null,
|
||||
renderer: renderer,
|
||||
tail: null,
|
||||
next: null,
|
||||
@ -818,6 +801,7 @@ export function createTView(
|
||||
viewCheckHooks: null,
|
||||
destroyHooks: null,
|
||||
pipeDestroyHooks: null,
|
||||
cleanup: null,
|
||||
hostBindings: null,
|
||||
components: null,
|
||||
directiveRegistry: typeof directives === 'function' ? directives() : directives,
|
||||
@ -915,19 +899,23 @@ export function listener(
|
||||
ngDevMode && assertPreviousIsParent();
|
||||
const node = previousOrParentNode;
|
||||
const native = node.native as RElement;
|
||||
ngDevMode && ngDevMode.rendererAddEventListener++;
|
||||
|
||||
// In order to match current behavior, native DOM event listeners must be added for all
|
||||
// events (including outputs).
|
||||
const cleanupFns = getCleanup();
|
||||
ngDevMode && ngDevMode.rendererAddEventListener++;
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
const wrappedListener = wrapListenerWithDirtyLogic(currentView, listenerFn);
|
||||
const cleanupFn = renderer.listen(native, eventName, wrappedListener);
|
||||
cleanupFns.push(cleanupFn, null);
|
||||
storeCleanupFn(currentView, cleanupFn);
|
||||
} else {
|
||||
const wrappedListener = wrapListenerWithDirtyAndDefault(currentView, listenerFn);
|
||||
native.addEventListener(eventName, wrappedListener, useCapture);
|
||||
cleanupFns.push(eventName, native, wrappedListener, useCapture);
|
||||
const cleanupInstances = getCleanup(currentView);
|
||||
cleanupInstances.push(wrappedListener);
|
||||
if (firstTemplatePass) {
|
||||
getTViewCleanup(currentView)
|
||||
.push(eventName, node.tNode.index, cleanupInstances !.length - 1, useCapture);
|
||||
}
|
||||
}
|
||||
|
||||
let tNode: TNode|null = node.tNode;
|
||||
@ -952,7 +940,39 @@ function createOutput(outputs: PropertyAliasValue, listener: Function): void {
|
||||
for (let i = 0; i < outputs.length; i += 2) {
|
||||
ngDevMode && assertDataInRange(outputs[i] as number, directives !);
|
||||
const subscription = directives ![outputs[i] as number][outputs[i + 1]].subscribe(listener);
|
||||
getCleanup().push(subscription.unsubscribe, subscription);
|
||||
storeCleanupWithContext(currentView, subscription, subscription.unsubscribe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves context for this cleanup function in LView.cleanupInstances.
|
||||
*
|
||||
* On the first template pass, saves in TView:
|
||||
* - Cleanup function
|
||||
* - Index of context we just saved in LView.cleanupInstances
|
||||
*/
|
||||
export function storeCleanupWithContext(
|
||||
view: LView = currentView, context: any, cleanupFn: Function): void {
|
||||
getCleanup(view).push(context);
|
||||
|
||||
if (view.tView.firstTemplatePass) {
|
||||
getTViewCleanup(view).push(cleanupFn, view.cleanupInstances !.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the cleanup function itself in LView.cleanupInstances.
|
||||
*
|
||||
* This is necessary for functions that are wrapped with their contexts, like in renderer2
|
||||
* listeners.
|
||||
*
|
||||
* On the first template pass, the index of the cleanup function is saved in TView.
|
||||
*/
|
||||
export function storeCleanupFn(view: LView, cleanupFn: Function): void {
|
||||
getCleanup(view).push(cleanupFn);
|
||||
|
||||
if (view.tView.firstTemplatePass) {
|
||||
getTViewCleanup(view).push(view.cleanupInstances !.length - 1, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,22 +63,14 @@ export interface LView {
|
||||
|
||||
/**
|
||||
* When a view is destroyed, listeners need to be released and outputs need to be
|
||||
* unsubscribed. This cleanup array stores both listener data (in chunks of 4)
|
||||
* and output data (in chunks of 2) for a particular view. Combining the arrays
|
||||
* saves on memory (70 bytes per array) and on a few bytes of code size (for two
|
||||
* separate for loops).
|
||||
* unsubscribed. This context array stores both listener functions wrapped with
|
||||
* their context and output subscription instances for a particular view.
|
||||
*
|
||||
* If it's a listener being stored:
|
||||
* 1st index is: event name to remove
|
||||
* 2nd index is: native element
|
||||
* 3rd index is: listener function
|
||||
* 4th index is: useCapture boolean
|
||||
*
|
||||
* If it's an output subscription:
|
||||
* 1st index is: unsubscribe function
|
||||
* 2nd index is: context for function
|
||||
* These change per LView instance, so they cannot be stored on TView. Instead,
|
||||
* TView.cleanup saves an index to the necessary context in this array.
|
||||
*/
|
||||
cleanup: any[]|null;
|
||||
// TODO: collapse into data[]
|
||||
cleanupInstances: any[]|null;
|
||||
|
||||
/**
|
||||
* The last LView or LContainer beneath this LView in the hierarchy.
|
||||
@ -368,6 +360,29 @@ export interface TView {
|
||||
*/
|
||||
pipeDestroyHooks: HookData|null;
|
||||
|
||||
/**
|
||||
* When a view is destroyed, listeners need to be released and outputs need to be
|
||||
* unsubscribed. This cleanup array stores both listener data (in chunks of 4)
|
||||
* and output data (in chunks of 2) for a particular view. Combining the arrays
|
||||
* saves on memory (70 bytes per array) and on a few bytes of code size (for two
|
||||
* separate for loops).
|
||||
*
|
||||
* If it's a native DOM listener being stored:
|
||||
* 1st index is: event name to remove
|
||||
* 2nd index is: index of native element in LView.data[]
|
||||
* 3rd index is: index of wrapped listener function in LView.cleanupInstances[]
|
||||
* 4th index is: useCapture boolean
|
||||
*
|
||||
* If it's a renderer2 style listener or ViewRef destroy hook being stored:
|
||||
* 1st index is: index of the cleanup function in LView.cleanupInstances[]
|
||||
* 2nd index is: null
|
||||
*
|
||||
* If it's an output subscription or query list destroy hook:
|
||||
* 1st index is: output unsubscribe function / query list destroy function
|
||||
* 2nd index is: index of function context in LView.cleanupInstances[]
|
||||
*/
|
||||
cleanup: any[]|null;
|
||||
|
||||
/**
|
||||
* A list of directive and element indices for child components that will need to be
|
||||
* refreshed when the current view has finished its check.
|
||||
|
@ -297,7 +297,7 @@ export function destroyViewTree(rootView: LView): void {
|
||||
} else if (viewOrContainer.next) {
|
||||
// Only move to the side and clean if operating below rootView -
|
||||
// otherwise we would start cleaning up sibling views of the rootView.
|
||||
cleanUpView(viewOrContainer as LView);
|
||||
cleanUpView(viewOrContainer);
|
||||
next = viewOrContainer.next;
|
||||
}
|
||||
|
||||
@ -306,10 +306,10 @@ export function destroyViewTree(rootView: LView): void {
|
||||
// with a root view that doesn't have children. We didn't descend into child views
|
||||
// so no need to go back up the views tree.
|
||||
while (viewOrContainer && !viewOrContainer !.next && viewOrContainer !== rootView) {
|
||||
cleanUpView(viewOrContainer as LView);
|
||||
cleanUpView(viewOrContainer);
|
||||
viewOrContainer = getParentState(viewOrContainer, rootView);
|
||||
}
|
||||
cleanUpView(viewOrContainer as LView || rootView);
|
||||
cleanUpView(viewOrContainer || rootView);
|
||||
|
||||
next = viewOrContainer && viewOrContainer.next;
|
||||
}
|
||||
@ -472,30 +472,42 @@ export function getParentState(state: LViewOrLContainer, rootView: LView): LView
|
||||
*
|
||||
* @param view The LView to clean up
|
||||
*/
|
||||
function cleanUpView(view: LView): void {
|
||||
removeListeners(view);
|
||||
executeOnDestroys(view);
|
||||
executePipeOnDestroys(view);
|
||||
// For component views only, the local renderer is destroyed as clean up time.
|
||||
if (view.tView && view.tView.id === -1 && isProceduralRenderer(view.renderer)) {
|
||||
ngDevMode && ngDevMode.rendererDestroy++;
|
||||
view.renderer.destroy();
|
||||
function cleanUpView(viewOrContainer: LViewOrLContainer): void {
|
||||
if ((viewOrContainer as LView).tView) {
|
||||
const view = viewOrContainer as LView;
|
||||
removeListeners(view);
|
||||
executeOnDestroys(view);
|
||||
executePipeOnDestroys(view);
|
||||
// For component views only, the local renderer is destroyed as clean up time.
|
||||
if (view.tView.id === -1 && isProceduralRenderer(view.renderer)) {
|
||||
ngDevMode && ngDevMode.rendererDestroy++;
|
||||
view.renderer.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes listeners and unsubscribes from output subscriptions */
|
||||
function removeListeners(view: LView): void {
|
||||
const cleanup = view.cleanup !;
|
||||
const cleanup = view.tView.cleanup !;
|
||||
if (cleanup != null) {
|
||||
for (let i = 0; i < cleanup.length - 1; i += 2) {
|
||||
if (typeof cleanup[i] === 'string') {
|
||||
cleanup ![i + 1].removeEventListener(cleanup[i], cleanup[i + 2], cleanup[i + 3]);
|
||||
// This is a listener with the native renderer
|
||||
const native = view.data[cleanup[i + 1]].native;
|
||||
const listener = view.cleanupInstances ![cleanup[i + 2]];
|
||||
native.removeEventListener(cleanup[i], listener, cleanup[i + 3]);
|
||||
i += 2;
|
||||
} else if (typeof cleanup[i] === 'number') {
|
||||
// This is a listener with renderer2 (cleanup fn can be found by index)
|
||||
const cleanupFn = view.cleanupInstances ![cleanup[i]];
|
||||
cleanupFn();
|
||||
} else {
|
||||
cleanup[i].call(cleanup[i + 1]);
|
||||
// This is a cleanup function that is grouped with the index of its context
|
||||
const context = view.cleanupInstances ![cleanup[i + 1]];
|
||||
cleanup[i].call(context);
|
||||
}
|
||||
}
|
||||
view.cleanup = null;
|
||||
view.cleanupInstances = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ import {getSymbolIterator} from '../util';
|
||||
|
||||
import {assertEqual, assertNotNull} from './assert';
|
||||
import {ReadFromInjectorFn, getOrCreateNodeInjectorForNode} from './di';
|
||||
import {assertPreviousIsParent, getCleanup, getCurrentQueries, store} from './instructions';
|
||||
import {assertPreviousIsParent, getCurrentQueries, store, storeCleanupWithContext} from './instructions';
|
||||
import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
|
||||
import {LInjector, unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
|
||||
import {LContainerNode, LElementNode, LNode, TNode, TNodeFlags, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
|
||||
@ -416,7 +416,7 @@ export function query<T>(
|
||||
const queryList = new QueryList<T>();
|
||||
const queries = getCurrentQueries(LQueries_);
|
||||
queries.track(queryList, predicate, descend, read);
|
||||
getCleanup().push(queryList.destroy, queryList);
|
||||
storeCleanupWithContext(undefined, queryList, queryList.destroy);
|
||||
if (memoryIndex != null) {
|
||||
store(memoryIndex, queryList);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_container_ref';
|
||||
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
|
||||
|
||||
import {checkNoChanges, detectChanges, markViewDirty} from './instructions';
|
||||
import {checkNoChanges, detectChanges, markViewDirty, storeCleanupFn} from './instructions';
|
||||
import {ComponentTemplate} from './interfaces/definition';
|
||||
import {LViewNode} from './interfaces/node';
|
||||
import {LView, LViewFlags} from './interfaces/view';
|
||||
@ -33,9 +33,7 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T> {
|
||||
|
||||
destroy(): void { destroyLView(this._view); }
|
||||
|
||||
onDestroy(callback: Function) {
|
||||
(this._view.cleanup || (this._view.cleanup = [])).push(callback, null);
|
||||
}
|
||||
onDestroy(callback: Function) { storeCleanupFn(this._view, callback); }
|
||||
|
||||
/**
|
||||
* Marks a view and all of its ancestors dirty.
|
||||
|
Reference in New Issue
Block a user