From d08222157cf81b5c480871f3683a79fe1476f2fb Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Thu, 10 Dec 2020 19:13:32 +0200 Subject: [PATCH] refactor(upgrade): create a helper for cleaning jqLite/jQuery data (#40045) This commit moves the code for cleaning jqLite/jQuery data on an element to a re-usable helper function. This way it is easier to keep the code consistent across all places where we need to clean data (now and in the future). PR Close #40045 --- .../common/src/downgrade_component_adapter.ts | 11 +++----- .../upgrade/src/common/src/upgrade_helper.ts | 12 ++------- packages/upgrade/src/common/src/util.ts | 25 ++++++++++++++++++- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/upgrade/src/common/src/downgrade_component_adapter.ts b/packages/upgrade/src/common/src/downgrade_component_adapter.ts index 5022a58aaca22..8711624f3bad5 100644 --- a/packages/upgrade/src/common/src/downgrade_component_adapter.ts +++ b/packages/upgrade/src/common/src/downgrade_component_adapter.ts @@ -8,10 +8,10 @@ import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges, StaticProvider, Testability, TestabilityRegistry, Type} from '@angular/core'; -import {element as angularElement, IAttributes, IAugmentedJQuery, ICompileService, INgModelController, IParseService, IScope} from './angular1'; +import {IAttributes, IAugmentedJQuery, ICompileService, INgModelController, IParseService, IScope} from './angular1'; import {PropertyBinding} from './component_info'; import {$SCOPE} from './constants'; -import {getTypeName, hookupNgModel, strictEquals} from './util'; +import {cleanData, getTypeName, hookupNgModel, strictEquals} from './util'; const INITIAL_VALUE = { __UNINITIALIZED__: true @@ -241,12 +241,7 @@ export class DowngradeComponentAdapter { // // To ensure the element is always properly cleaned up, we manually call `cleanData()` on // this element and its descendants before destroying the `ComponentRef`. - // - // NOTE: - // `cleanData()` also will invoke the AngularJS `$destroy` event on the element: - // https://github.com/angular/angular.js/blob/2e72ea13fa98bebf6ed4b5e3c45eaf5f990ed16f/src/Angular.js#L1932-L1945 - angularElement.cleanData(this.element); - angularElement.cleanData((this.element[0] as Element).querySelectorAll('*')); + cleanData(this.element[0]); destroyComponentRef(); } diff --git a/packages/upgrade/src/common/src/upgrade_helper.ts b/packages/upgrade/src/common/src/upgrade_helper.ts index 0d520eb9e6a4b..71a0c56f9233c 100644 --- a/packages/upgrade/src/common/src/upgrade_helper.ts +++ b/packages/upgrade/src/common/src/upgrade_helper.ts @@ -10,7 +10,7 @@ import {ElementRef, Injector, SimpleChanges} from '@angular/core'; import {DirectiveRequireProperty, element as angularElement, IAugmentedJQuery, ICloneAttachFunction, ICompileService, IController, IControllerService, IDirective, IHttpBackendService, IInjectorService, ILinkFn, IScope, ITemplateCacheService, SingleOrListOrMap} from './angular1'; import {$COMPILE, $CONTROLLER, $HTTP_BACKEND, $INJECTOR, $TEMPLATE_CACHE} from './constants'; -import {controllerKey, directiveNormalize, isFunction} from './util'; +import {cleanData, controllerKey, directiveNormalize, isFunction} from './util'; @@ -125,15 +125,7 @@ export class UpgradeHelper { controllerInstance.$onDestroy(); } $scope.$destroy(); - - // Clean the jQuery/jqLite data on the component+child elements. - // Equivelent to how jQuery/jqLite invoke `cleanData` on an Element (this.element) - // https://github.com/jquery/jquery/blob/e743cbd28553267f955f71ea7248377915613fd9/src/manipulation.js#L223 - // https://github.com/angular/angular.js/blob/26ddc5f830f902a3d22f4b2aab70d86d4d688c82/src/jqLite.js#L306-L312 - // `cleanData` will invoke the AngularJS `$destroy` DOM event - // https://github.com/angular/angular.js/blob/26ddc5f830f902a3d22f4b2aab70d86d4d688c82/src/Angular.js#L1911-L1924 - angularElement.cleanData([this.element]); - angularElement.cleanData(this.element.querySelectorAll('*')); + cleanData(this.element); } prepareTransclusion(): ILinkFn|undefined { diff --git a/packages/upgrade/src/common/src/util.ts b/packages/upgrade/src/common/src/util.ts index 1c772f9bb4213..bb93e23a637ff 100644 --- a/packages/upgrade/src/common/src/util.ts +++ b/packages/upgrade/src/common/src/util.ts @@ -8,7 +8,7 @@ import {Injector, Type} from '@angular/core'; -import {IInjectorService, INgModelController} from './angular1'; +import {element as angularElement, IInjectorService, INgModelController} from './angular1'; import {DOWNGRADED_MODULE_COUNT_KEY, UPGRADE_APP_TYPE_KEY} from './constants'; const DIRECTIVE_PREFIX_REGEXP = /^(?:x|data)[:\-_]/i; @@ -25,6 +25,25 @@ export function onError(e: any) { throw e; } +/** + * Clean the jqLite/jQuery data on the element and all its descendants. + * Equivalent to how jqLite/jQuery invoke `cleanData()` on an Element when removed: + * https://github.com/angular/angular.js/blob/2e72ea13fa98bebf6ed4b5e3c45eaf5f990ed16f/src/jqLite.js#L349-L355 + * https://github.com/jquery/jquery/blob/6984d1747623dbc5e87fd6c261a5b6b1628c107c/src/manipulation.js#L182 + * + * NOTE: + * `cleanData()` will also invoke the AngularJS `$destroy` DOM event on the element: + * https://github.com/angular/angular.js/blob/2e72ea13fa98bebf6ed4b5e3c45eaf5f990ed16f/src/Angular.js#L1932-L1945 + * + * @param node The DOM node whose data needs to be cleaned. + */ +export function cleanData(node: Node): void { + angularElement.cleanData([node]); + if (isParentNode(node)) { + angularElement.cleanData(node.querySelectorAll('*')); + } +} + export function controllerKey(name: string): string { return '$' + name + 'Controller'; } @@ -53,6 +72,10 @@ export function isFunction(value: any): value is Function { return typeof value === 'function'; } +function isParentNode(node: Node|ParentNode): node is ParentNode { + return isFunction((node as unknown as ParentNode).querySelectorAll); +} + export function validateInjectionKey( $injector: IInjectorService, downgradedModule: string, injectionKey: string, attemptedAction: string): void {