Skip to content

Commit

Permalink
fixup! refactor(core): defer triggers cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewKushnir committed Oct 20, 2023
1 parent 7a07176 commit 9070c5e
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 98 deletions.
82 changes: 21 additions & 61 deletions packages/core/src/defer/cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,79 +6,39 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ɵɵdefineInjectable} from '../di';

import {CLEANUP_MANAGER, CleanupKey, DeferCleanupManager as IDeferCleanupManager, LDeferBlockDetails, TDeferBlockDetails} from './interfaces';
import {LDeferBlockDetails, PREFETCH_TRIGGER_CLEANUP_FNS, TRIGGER_CLEANUP_FNS} from './interfaces';

/**
* Registers a cleanup function associated with a prefetching trigger
* or a regular trigger of a defer block.
*/
export function storeTriggerCleanupFn(
isPrefetch: boolean, tDetails: TDeferBlockDetails, lDetails: LDeferBlockDetails,
cleanupFn: VoidFunction) {
const key = getCleanupKey(isPrefetch, tDetails, lDetails);
lDetails[CLEANUP_MANAGER].add(key, cleanupFn);
}

/**
* Invokes all registered cleanup functions for prefetch and regular triggers.
*/
export function invokeTriggerCleanupFns(
isPrefetch: boolean, tDetails: TDeferBlockDetails, lDetails: LDeferBlockDetails) {
const key = getCleanupKey(isPrefetch, tDetails, lDetails);
lDetails[CLEANUP_MANAGER].invoke(key);
isPrefetch: boolean, lDetails: LDeferBlockDetails, cleanupFn: VoidFunction) {
const key = isPrefetch ? PREFETCH_TRIGGER_CLEANUP_FNS : TRIGGER_CLEANUP_FNS;
if (lDetails[key] === null) {
lDetails[key] = [];
}
lDetails[key]!.push(cleanupFn);
}

/**
* Returns an object that should be used as a key.
*
* For prefetching we use TDetails as a key, because the process of loading
* dependencies affects all instances. For regular triggers we use LDetails
* as a key, since they are instance-specific.
* Invokes registered cleanup functions either for prefetch or for regular triggers.
*/
function getCleanupKey(
isPrefetch: boolean, tDetails: TDeferBlockDetails, lDetails: LDeferBlockDetails) {
return isPrefetch ? tDetails : lDetails;
export function invokeTriggerCleanupFns(isPrefetch: boolean, lDetails: LDeferBlockDetails) {
const key = isPrefetch ? PREFETCH_TRIGGER_CLEANUP_FNS : TRIGGER_CLEANUP_FNS;
const cleanupFns = lDetails[key];
if (cleanupFns !== null) {
for (const cleanupFn of cleanupFns) {
cleanupFn();
}
lDetails[key] = null;
}
}

/**
* Internal service to keep track of cleanup functions associated
* with defer blocks. This class is used to manage cleanup functions
* created for prefetch and regular triggers.
* Invokes registered cleanup functions for both prefetch and regular triggers.
*/
export class DeferCleanupManager implements IDeferCleanupManager {
private callbacks = new Map<CleanupKey, Set<VoidFunction>>();

add(key: CleanupKey, callback: VoidFunction) {
const callbacks = this.callbacks;
if (!callbacks.has(key)) {
callbacks.set(key, new Set());
}
callbacks.get(key)!.add(callback);
}

invoke(key: CleanupKey) {
const callbacks = this.callbacks.get(key);
if (callbacks) {
for (const callback of callbacks) {
callback();
}
callbacks.clear();
}
}

ngOnDestroy() {
for (const [block] of this.callbacks) {
this.invoke(block);
}
this.callbacks.clear();
}

/** @nocollapse */
static ɵprov = /** @pureOrBreakMyCode */ ɵɵdefineInjectable({
token: DeferCleanupManager,
providedIn: 'root',
factory: () => new DeferCleanupManager(),
});
export function invokeAllTriggerCleanupFns(lDetails: LDeferBlockDetails) {
invokeTriggerCleanupFns(true /* isPrefetch */, lDetails);
invokeTriggerCleanupFns(false /* isPrefetch */, lDetails);
}
7 changes: 3 additions & 4 deletions packages/core/src/defer/dom_triggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import {assertLContainer, assertLView} from '../render3/assert';
import {CONTAINER_HEADER_OFFSET} from '../render3/interfaces/container';
import {TNode} from '../render3/interfaces/node';
import {isDestroyed} from '../render3/interfaces/type_checks';
import {HEADER_OFFSET, INJECTOR, LView, TVIEW} from '../render3/interfaces/view';
import {HEADER_OFFSET, INJECTOR, LView} from '../render3/interfaces/view';
import {getNativeByIndex, removeLViewOnDestroy, storeLViewOnDestroy, walkUpViews} from '../render3/util/view_utils';
import {assertElement, assertEqual} from '../util/assert';
import {NgZone} from '../zone';
import {storeTriggerCleanupFn} from './cleanup';

import {DEFER_BLOCK_STATE, DeferBlockInternalState, DeferBlockState} from './interfaces';
import {getLDeferBlockDetails, getTDeferBlockDetails} from './utils';
import {getLDeferBlockDetails} from './utils';

/** Configuration object used to register passive and capturing events. */
const eventListenerOptions: AddEventListenerOptions = {
Expand Down Expand Up @@ -305,8 +305,7 @@ export function registerDomTrigger(
storeLViewOnDestroy(triggerLView, cleanup);
}

const tDetails = getTDeferBlockDetails(initialLView[TVIEW], tNode);
storeTriggerCleanupFn(isPrefetch, tDetails, lDetails, cleanup);
storeTriggerCleanupFn(isPrefetch, lDetails, cleanup);
}

// Begin polling for the trigger.
Expand Down
22 changes: 10 additions & 12 deletions packages/core/src/defer/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {getConstant, getTNode, removeLViewOnDestroy, storeLViewOnDestroy} from '
import {addLViewToLContainer, createAndRenderEmbeddedLView, removeLViewFromLContainer, shouldAddViewToDom} from '../render3/view_manipulation';
import {assertDefined, throwError} from '../util/assert';

import {DeferCleanupManager, invokeTriggerCleanupFns, storeTriggerCleanupFn} from './cleanup';
import {invokeAllTriggerCleanupFns, invokeTriggerCleanupFns, storeTriggerCleanupFn} from './cleanup';
import {onHover, onInteraction, onViewport, registerDomTrigger} from './dom_triggers';
import {onIdle} from './idle_scheduler';
import {DEFER_BLOCK_STATE, DeferBlockBehavior, DeferBlockConfig, DeferBlockDependencyInterceptor, DeferBlockInternalState, DeferBlockState, DeferDependenciesLoadingState, DeferredLoadingBlockConfig, DeferredPlaceholderBlockConfig, DependencyResolverFn, LDeferBlockDetails, LOADING_AFTER_CLEANUP_FN, NEXT_DEFER_BLOCK_STATE, STATE_IS_FROZEN_UNTIL, TDeferBlockDetails} from './interfaces';
Expand Down Expand Up @@ -147,23 +147,22 @@ export function ɵɵdefer(
// In client-only mode, this function is a noop.
populateDehydratedViewsInLContainer(lContainer, tNode, lView);

const injector = lView[INJECTOR]!;
const cleanupManager = injector.get(DeferCleanupManager);

// Init instance-specific defer details and store it.
const lDetails: LDeferBlockDetails = [
null, // NEXT_DEFER_BLOCK_STATE
DeferBlockInternalState.Initial, // DEFER_BLOCK_STATE
null, // STATE_IS_FROZEN_UNTIL
null, // LOADING_AFTER_CLEANUP_FN
cleanupManager // CLEANUP_MANAGER
null, // TRIGGER_CLEANUP_FNS
null // PREFETCH_TRIGGER_CLEANUP_FNS
];
setLDeferBlockDetails(lView, adjustedIndex, lDetails);

const cleanupTriggersFn = () => cleanupManager.invoke(lDetails);
const cleanupTriggersFn = () => invokeAllTriggerCleanupFns(lDetails);

// When defer block is triggered - unsubscribe from LView destroy cleanup.
cleanupManager.add(lDetails, () => removeLViewOnDestroy(lView, cleanupTriggersFn));
storeTriggerCleanupFn(
false /* isPrefetch */, lDetails, () => removeLViewOnDestroy(lView, cleanupTriggersFn));
storeLViewOnDestroy(lView, cleanupTriggersFn);
}

Expand Down Expand Up @@ -401,8 +400,7 @@ function scheduleDelayedTrigger(
renderPlaceholder(lView, tNode);
const cleanupFn = scheduleFn(() => triggerDeferBlock(lView, tNode), lView);
const lDetails = getLDeferBlockDetails(lView, tNode);
const tDetails = getTDeferBlockDetails(lView[TVIEW], tNode);
storeTriggerCleanupFn(false /* isPrefetch */, tDetails, lDetails, cleanupFn);
storeTriggerCleanupFn(false /* isPrefetch */, lDetails, cleanupFn);
}

/**
Expand All @@ -421,7 +419,7 @@ function scheduleDelayedPrefetching(
const lDetails = getLDeferBlockDetails(lView, tNode);
const prefetch = () => triggerPrefetching(tDetails, lView, tNode);
const cleanupFn = scheduleFn(prefetch, lView);
storeTriggerCleanupFn(true /* isPrefetch */, tDetails, lDetails, cleanupFn);
storeTriggerCleanupFn(true /* isPrefetch */, lDetails, cleanupFn);
}
}

Expand Down Expand Up @@ -614,7 +612,7 @@ export function triggerResourceLoading(tDetails: TDeferBlockDetails, lView: LVie
tDetails.loadingState = DeferDependenciesLoadingState.IN_PROGRESS;

// Prefetching is triggered, cleanup all registered prefetch triggers.
invokeTriggerCleanupFns(true /* isPrefetch */, tDetails, lDetails);
invokeTriggerCleanupFns(true /* isPrefetch */, lDetails);

let dependenciesFn = tDetails.dependencyResolverFn;

Expand Down Expand Up @@ -735,7 +733,7 @@ function triggerDeferBlock(lView: LView, tNode: TNode) {
const tDetails = getTDeferBlockDetails(tView, tNode);

// Defer block is triggered, cleanup all registered trigger functions.
invokeTriggerCleanupFns(false /* isPrefetch */, tDetails, lDetails);
invokeTriggerCleanupFns(false /* isPrefetch */, lDetails);

switch (tDetails.loadingState) {
case DeferDependenciesLoadingState.NOT_STARTED:
Expand Down
30 changes: 9 additions & 21 deletions packages/core/src/defer/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,6 @@

import type {DependencyType} from '../render3/interfaces/definition';

/**
* Type of a key that can be used in `DeferCleanupManager` APIs.
*
* For prefetching we use TDetails as a key, because the process of loading
* dependencies affects all instances. For regular triggers we use LDetails
* as a key, since they are instance-specific.
*/
export type CleanupKey = TDeferBlockDetails|LDeferBlockDetails;

/**
* Defines `DeferCleanupManager` API, see `DeferCleanupManager` implementation
* for additional information.
*/
export interface DeferCleanupManager {
add(key: CleanupKey, callback: VoidFunction): void;
invoke(key: CleanupKey): void;
}

/**
* Describes the shape of a function generated by the compiler
* to download dependencies that can be defer-loaded.
Expand Down Expand Up @@ -165,7 +147,8 @@ export const NEXT_DEFER_BLOCK_STATE = 0;
export const DEFER_BLOCK_STATE = 1;
export const STATE_IS_FROZEN_UNTIL = 2;
export const LOADING_AFTER_CLEANUP_FN = 3;
export const CLEANUP_MANAGER = 4;
export const TRIGGER_CLEANUP_FNS = 4;
export const PREFETCH_TRIGGER_CLEANUP_FNS = 5;

/**
* Describes instance-specific defer block data.
Expand Down Expand Up @@ -199,9 +182,14 @@ export interface LDeferBlockDetails extends Array<unknown> {
[LOADING_AFTER_CLEANUP_FN]: VoidFunction|null;

/**
* A reference to a cleanup manager instance shared across all defer blocks.
* List of cleanup functions for regular triggers.
*/
[TRIGGER_CLEANUP_FNS]: VoidFunction[]|null;

/**
* List of cleanup functions for prefetch triggers.
*/
[CLEANUP_MANAGER]: DeferCleanupManager;
[PREFETCH_TRIGGER_CLEANUP_FNS]: VoidFunction[]|null;
}

/**
Expand Down

0 comments on commit 9070c5e

Please sign in to comment.