Skip to content

Commit 513a4fe

Browse files
crisbetothePunderWoman
authored andcommitted
refactor(core): replace usages of removeChild (#57203)
These changes replace most usages of `removeChild` with `remove`. The latter has the advantage of not having to look up the `parentNode` and ensure that the child being removed actually belongs to the specific parent. The refactor should be fairly safe since all the browsers we cover support `remove`. [Something similar was done in Components](angular/components#23592) some time ago and there haven't been any bug reports as a result. PR Close #57203
1 parent d465061 commit 513a4fe

32 files changed

+53
-95
lines changed

packages/animations/browser/src/render/animation_renderer.ts

+2-9
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,8 @@ export class AnimationRendererFactory implements RendererFactory2 {
2929
private engine: AnimationEngine,
3030
private _zone: NgZone,
3131
) {
32-
engine.onRemovalComplete = (element: any, delegate: Renderer2) => {
33-
// Note: if a component element has a leave animation, and a host leave animation,
34-
// the view engine will call `removeChild` for the parent
35-
// component renderer as well as for the child component renderer.
36-
// Therefore, we need to check if we already removed the element.
37-
const parentNode = delegate?.parentNode(element);
38-
if (parentNode) {
39-
delegate.removeChild(parentNode, element);
40-
}
32+
engine.onRemovalComplete = (element: any, delegate: Renderer2 | null) => {
33+
delegate?.removeChild(null, element);
4134
};
4235
}
4336

packages/animations/browser/src/render/renderer.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,13 @@ export class BaseAnimationRenderer implements Renderer2 {
7878
}
7979

8080
removeChild(parent: any, oldChild: any, isHostElement?: boolean): void {
81-
this.engine.onRemove(this.namespaceId, oldChild, this.delegate);
81+
// Prior to the changes in #57203, this method wasn't being called at all by `core` if the child
82+
// doesn't have a parent. There appears to be some animation-specific downstream logic that
83+
// depends on the null check happening before the animation engine. This check keeps the old
84+
// behavior while allowing `core` to not have to check for the parent element anymore.
85+
if (this.parentNode(oldChild)) {
86+
this.engine.onRemove(this.namespaceId, oldChild, this.delegate);
87+
}
8288
}
8389

8490
selectRootElement(selectorOrNode: any, preserveContent?: boolean) {

packages/animations/browser/test/dsl/animation_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('Animation', () => {
5656
});
5757

5858
afterEach(() => {
59-
document.body.removeChild(rootElement);
59+
rootElement.remove();
6060
});
6161

6262
describe('validation', () => {

packages/animations/browser/test/dsl/animation_trigger_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('AnimationTrigger', () => {
2929
});
3030

3131
afterEach(() => {
32-
document.body.removeChild(element);
32+
element.remove();
3333
});
3434

3535
describe('trigger validation', () => {

packages/animations/browser/test/render/timeline_animation_engine_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/src/mock_a
3939
document.body.appendChild(element);
4040
});
4141

42-
afterEach(() => document.body.removeChild(element));
42+
afterEach(() => element.remove());
4343

4444
it('should animate a timeline', () => {
4545
const engine = makeEngine(getBodyNode());

packages/animations/browser/test/render/transition_animation_engine_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const DEFAULT_NAMESPACE_ID = 'id';
4949
});
5050

5151
afterEach(() => {
52-
document.body.removeChild(element);
52+
element.remove();
5353
});
5454

5555
function makeEngine(normalizer?: AnimationStyleNormalizer) {

packages/common/http/src/jsonp.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,7 @@ export class JsonpClientBackend implements HttpBackend {
164164
// success, error, and cancellation paths, so it's extracted out for convenience.
165165
const cleanup = () => {
166166
// Remove the <script> tag if it's still on the page.
167-
if (node.parentNode) {
168-
node.parentNode.removeChild(node);
169-
}
167+
node.remove();
170168

171169
// Remove the response callback from the callbackMap (window object in the
172170
// browser).

packages/common/http/test/jsonp_mock.ts

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export class MockScriptElement {
2121
removeEventListener(event: 'load' | 'error'): void {
2222
delete this.listeners[event];
2323
}
24+
25+
remove() {
26+
this.ownerDocument.removeNode(this);
27+
}
2428
}
2529

2630
export class MockDocument {

packages/common/test/viewport_scroller_spec.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ describe('BrowserViewportScroller', () => {
108108
return {
109109
anchorNode,
110110
cleanup: () => {
111-
document.body.removeChild(tallItem);
112-
document.body.removeChild(anchorNode);
111+
tallItem.remove();
112+
anchorNode.remove();
113113
},
114114
};
115115
}
@@ -128,8 +128,8 @@ describe('BrowserViewportScroller', () => {
128128
return {
129129
anchorNode,
130130
cleanup: () => {
131-
document.body.removeChild(tallItem);
132-
document.body.removeChild(elementWithShadowRoot);
131+
tallItem.remove();
132+
elementWithShadowRoot.remove();
133133
},
134134
};
135135
}

packages/core/src/render3/interfaces/renderer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export interface Renderer {
4343
destroyNode?: ((node: RNode) => void) | null;
4444
appendChild(parent: RElement, newChild: RNode): void;
4545
insertBefore(parent: RNode, newChild: RNode, refChild: RNode | null, isMove?: boolean): void;
46-
removeChild(parent: RElement, oldChild: RNode, isHostElement?: boolean): void;
46+
removeChild(parent: RElement | null, oldChild: RNode, isHostElement?: boolean): void;
4747
selectRootElement(selectorOrNode: string | any, preserveContent?: boolean): RElement;
4848

4949
parentNode(node: RNode): RElement | null;

packages/core/src/render3/interfaces/renderer_dom.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,6 @@ export interface RNode {
3434
*/
3535
nextSibling: RNode | null;
3636

37-
/**
38-
* Removes a child from the current node and returns the removed node
39-
* @param oldChild the child node to remove
40-
*/
41-
removeChild(oldChild: RNode): RNode;
42-
4337
/**
4438
* Insert a child node.
4539
*
@@ -77,7 +71,7 @@ export interface RElement extends RNode {
7771
): void;
7872
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
7973
removeEventListener(type: string, listener?: EventListener, options?: boolean): void;
80-
74+
remove(): void;
8175
setProperty?(name: string, value: any): void;
8276
}
8377

packages/core/src/render3/node_manipulation.ts

+1-19
Original file line numberDiff line numberDiff line change
@@ -711,21 +711,6 @@ function nativeAppendOrInsertBefore(
711711
}
712712
}
713713

714-
/** Removes a node from the DOM given its native parent. */
715-
function nativeRemoveChild(
716-
renderer: Renderer,
717-
parent: RElement,
718-
child: RNode,
719-
isHostElement?: boolean,
720-
): void {
721-
renderer.removeChild(parent, child, isHostElement);
722-
}
723-
724-
/** Checks if an element is a `<template>` node. */
725-
function isTemplateNode(node: RElement): node is RTemplate {
726-
return node.tagName === 'TEMPLATE' && (node as RTemplate).content !== undefined;
727-
}
728-
729714
/**
730715
* Returns a native parent of a given native node.
731716
*/
@@ -951,10 +936,7 @@ export function getBeforeNodeForView(
951936
*/
952937
export function nativeRemoveNode(renderer: Renderer, rNode: RNode, isHostElement?: boolean): void {
953938
ngDevMode && ngDevMode.rendererRemoveNode++;
954-
const nativeParent = nativeParentNode(renderer, rNode);
955-
if (nativeParent) {
956-
nativeRemoveChild(renderer, nativeParent, rNode, isHostElement);
957-
}
939+
renderer.removeChild(null, rNode, isHostElement);
958940
}
959941

960942
/**

packages/core/src/sanitization/html_sanitizer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): Trusted
335335
if (inertBodyElement) {
336336
const parent = getTemplateContent(inertBodyElement) || inertBodyElement;
337337
while (parent.firstChild) {
338-
parent.removeChild(parent.firstChild);
338+
parent.firstChild.remove();
339339
}
340340
}
341341
}

packages/core/src/sanitization/inert_body.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class DOMParserHelper implements InertBodyHelper {
5151
// the `inertDocumentHelper` instead.
5252
return this.inertDocumentHelper.getInertBodyElement(html);
5353
}
54-
body.removeChild(body.firstChild!);
54+
body.firstChild?.remove();
5555
return body;
5656
} catch {
5757
return null;

packages/core/test/acceptance/renderer_factory_spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -548,8 +548,8 @@ class MockRenderer implements Renderer2 {
548548
insertBefore(parent: Node, newChild: Node, refChild: Node | null): void {
549549
parent.insertBefore(newChild, refChild);
550550
}
551-
removeChild(parent: RElement, oldChild: Node): void {
552-
parent.removeChild(oldChild);
551+
removeChild(parent: RElement, oldChild: Element): void {
552+
oldChild.remove();
553553
}
554554
selectRootElement(selectorOrNode: string | any): RElement {
555555
return typeof selectorOrNode === 'string'

packages/core/test/acceptance/view_container_ref_spec.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -2621,9 +2621,7 @@ describe('ViewContainerRef', () => {
26212621
containerEl!.appendChild(rootEl);
26222622
},
26232623
removeAllRootElements() {
2624-
if (containerEl) {
2625-
containerEl.parentNode?.removeChild(containerEl);
2626-
}
2624+
containerEl?.remove();
26272625
},
26282626
};
26292627
}

packages/core/test/bundling/defer/bundle.golden_symbols.json

-3
Original file line numberDiff line numberDiff line change
@@ -2285,9 +2285,6 @@
22852285
{
22862286
"name": "nativeInsertBefore"
22872287
},
2288-
{
2289-
"name": "nativeParentNode"
2290-
},
22912288
{
22922289
"name": "nextNgElementId"
22932290
},

packages/core/test/bundling/hydration/bundle.golden_symbols.json

-3
Original file line numberDiff line numberDiff line change
@@ -1166,9 +1166,6 @@
11661166
{
11671167
"name": "nativeInsertBefore"
11681168
},
1169-
{
1170-
"name": "nativeParentNode"
1171-
},
11721169
{
11731170
"name": "nativeRemoveNode"
11741171
},

packages/core/test/dom/dom_adapter_spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('dom adapter', () => {
4949
headEl.appendChild(baseEl);
5050

5151
const baseHref = getDOM().getBaseHref(defaultDoc);
52-
headEl.removeChild(baseEl);
52+
baseEl.remove();
5353
getDOM().resetBaseElement();
5454

5555
expect(baseHref).toEqual('/drop/bass/connon/');
@@ -62,7 +62,7 @@ describe('dom adapter', () => {
6262
headEl.appendChild(baseEl);
6363

6464
const baseHref = getDOM().getBaseHref(defaultDoc)!;
65-
headEl.removeChild(baseEl);
65+
baseEl.remove();
6666
getDOM().resetBaseElement();
6767

6868
expect(baseHref.endsWith('/base')).toBe(true);

packages/core/test/render3/instructions/mock_renderer_factory.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ class MockRenderer implements Renderer {
4141
insertBefore(parent: Node, newChild: Node, refChild: Node | null): void {
4242
parent.insertBefore(newChild, refChild);
4343
}
44-
removeChild(parent: RElement, oldChild: Node): void {
45-
parent.removeChild(oldChild);
44+
removeChild(_parent: RElement | null, oldChild: RElement): void {
45+
oldChild.remove();
4646
}
4747
selectRootElement(selectorOrNode: string | any): RElement {
4848
return typeof selectorOrNode === 'string'

packages/core/test/transfer_state_spec.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ import {getDocument} from '../src/render3/interfaces/document';
1313
import {makeStateKey, TransferState} from '../src/transfer_state';
1414

1515
function removeScriptTag(doc: Document, id: string) {
16-
const existing = doc.getElementById(id);
17-
if (existing) {
18-
doc.body.removeChild(existing);
19-
}
16+
doc.getElementById(id)?.remove();
2017
}
2118

2219
function addScriptTag(doc: Document, appId: string, data: object | string) {

packages/elements/test/create-custom-element-env_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('createCustomElement with env injector', () => {
2020
});
2121

2222
afterEach(() => {
23-
document.body.removeChild(testContainer);
23+
testContainer.remove();
2424
(testContainer as any) = null;
2525
});
2626

packages/elements/test/create-custom-element_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('createCustomElement', () => {
6161

6262
afterAll(() => {
6363
destroyPlatform();
64-
document.body.removeChild(testContainer);
64+
testContainer.remove();
6565
(testContainer as any) = null;
6666
});
6767

packages/platform-browser/animations/async/test/animation_renderer_spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,8 @@ type AnimationBrowserModule = typeof import('@angular/animations/browser');
122122
it("should hook into the engine's insert operations when removing children", async () => {
123123
const renderer = await makeRenderer();
124124
const engine = (renderer as any).delegate.engine as MockAnimationEngine;
125-
const container = el('<div></div>');
126125

127-
renderer.removeChild(container, element, false);
126+
renderer.removeChild(null, element, false);
128127
expect(engine.captures['onRemove'].pop()).toEqual([element]);
129128
});
130129

packages/platform-browser/animations/test/animation_renderer_spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,8 @@ import {el} from '../../testing/src/browser_util';
9191
it("should hook into the engine's insert operations when removing children", () => {
9292
const renderer = makeRenderer();
9393
const engine = TestBed.inject(AnimationEngine) as MockAnimationEngine;
94-
const container = el('<div></div>');
9594

96-
renderer.removeChild(container, element);
95+
renderer.removeChild(null, element);
9796
expect(engine.captures['onRemove'].pop()).toEqual([element]);
9897
});
9998

packages/platform-browser/src/browser/browser_adapter.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
3535
el.dispatchEvent(evt);
3636
}
3737
override remove(node: Node): void {
38-
if (node.parentNode) {
39-
node.parentNode.removeChild(node);
40-
}
38+
(node as Element | Text | Comment).remove();
4139
}
4240
override createElement(tagName: string, doc?: Document): HTMLElement {
4341
doc = doc || this.getDefaultDocument();

packages/platform-browser/src/dom/dom_renderer.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,8 @@ class DefaultDomRenderer2 implements Renderer2 {
243243
}
244244
}
245245

246-
removeChild(parent: any, oldChild: any): void {
247-
if (parent) {
248-
parent.removeChild(oldChild);
249-
}
246+
removeChild(_parent: any, oldChild: any): void {
247+
oldChild.remove();
250248
}
251249

252250
selectRootElement(selectorOrNode: string | any, preserveContent?: boolean): any {
@@ -448,8 +446,8 @@ class ShadowDomRenderer extends DefaultDomRenderer2 {
448446
override insertBefore(parent: any, newChild: any, refChild: any): void {
449447
return super.insertBefore(this.nodeOrShadowRoot(parent), newChild, refChild);
450448
}
451-
override removeChild(parent: any, oldChild: any): void {
452-
return super.removeChild(this.nodeOrShadowRoot(parent), oldChild);
449+
override removeChild(_parent: any, oldChild: any): void {
450+
return super.removeChild(null, oldChild);
453451
}
454452
override parentNode(node: any): any {
455453
return this.nodeOrShadowRoot(super.parentNode(this.nodeOrShadowRoot(node)));

packages/platform-browser/test/dom/dom_renderer_spec.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,12 @@ describe('DefaultDomRendererV2', () => {
9898
});
9999

100100
describe('removeChild', () => {
101-
it('should not error when removing a child with a different parent than given', () => {
102-
const savedParent = document.createElement('div');
103-
const realParent = document.createElement('div');
101+
it('should not error when removing a child without passing a parent', () => {
102+
const parent = document.createElement('div');
104103
const child = document.createElement('div');
105104

106-
realParent.appendChild(child);
107-
renderer.removeChild(savedParent, child);
105+
parent.appendChild(child);
106+
renderer.removeChild(null, child);
108107
});
109108
});
110109

packages/platform-browser/test/dom/events/event_manager_spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ import {TestBed} from '@angular/core/testing';
325325
expect(timeoutId).not.toBe(null);
326326

327327
// cleanup the DOM by removing the test element we attached earlier.
328-
doc.body.removeChild(element);
328+
element.remove();
329329
timeoutId && clearTimeout(timeoutId);
330330
});
331331

packages/platform-server/test/hydration_spec.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -4102,8 +4102,7 @@ describe('platform-server hydration integration', () => {
41024102
el = inject(ElementRef);
41034103

41044104
ngAfterViewInit() {
4105-
const pTag = document.querySelector('p');
4106-
pTag?.parentElement?.removeChild(pTag);
4105+
document.querySelector('p')?.remove();
41074106
const span = document.createElement('span');
41084107
span.innerHTML = 'Appended span';
41094108
this.el.nativeElement.appendChild(span);

packages/upgrade/src/common/src/upgrade_helper.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ export class UpgradeHelper {
261261
let childNode: Node | null;
262262

263263
while ((childNode = this.element.firstChild)) {
264-
this.element.removeChild(childNode);
264+
(childNode as Element | Comment | Text).remove();
265265
childNodes.push(childNode);
266266
}
267267

0 commit comments

Comments
 (0)