feat(ivy): support renderer.destroy and renderer.destroyNode hooks (#24049)
PR Close #24049
This commit is contained in:
parent
f6f44edcc0
commit
2e21690c66
|
@ -228,10 +228,10 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
|
||||||
### View Encapsulation
|
### View Encapsulation
|
||||||
| Feature | Runtime | Spec | Compiler |
|
| Feature | Runtime | Spec | Compiler |
|
||||||
| ----------------------------------- | ------- | -------- | -------- |
|
| ----------------------------------- | ------- | -------- | -------- |
|
||||||
| Render3.None | ✅ | ✅ | ✅ |
|
| Renderer3.None | ✅ | ✅ | ✅ |
|
||||||
| Render2.None | ✅ | ✅ | ✅ |
|
| Renderer2.None | ✅ | ✅ | ✅ |
|
||||||
| Render2.Emulated | ❌ | ❌ | ❌ |
|
| Renderer2.Emulated | ❌ | ❌ | ❌ |
|
||||||
| Render2.Native | ❌ | ❌ | ❌ |
|
| Renderer2.Native | ❌ | ❌ | ❌ |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -254,3 +254,28 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
|
||||||
| `checkNoChanges()` | n/a | n/a | ❌ | n/a | n/a | ✅ |
|
| `checkNoChanges()` | n/a | n/a | ❌ | n/a | n/a | ✅ |
|
||||||
| `reattach()` | n/a | n/a | ❌ | n/a | n/a | ✅ |
|
| `reattach()` | n/a | n/a | ❌ | n/a | n/a | ✅ |
|
||||||
| `nativeElement()` | n/a | n/a | n/a | n/a | ✅ | n/a |
|
| `nativeElement()` | n/a | n/a | n/a | n/a | ✅ | n/a |
|
||||||
|
|
||||||
|
### Renderer2
|
||||||
|
| Method | Runtime |
|
||||||
|
| ----------------------------------- | ------- |
|
||||||
|
| `data()` | n/a |
|
||||||
|
| `destroy()` | ✅ |
|
||||||
|
| `createElement()` | ✅ |
|
||||||
|
| `createComment()` | n/a |
|
||||||
|
| `createText()` | ✅ |
|
||||||
|
| `destroyNode()` | ✅ |
|
||||||
|
| `appendChild()` | ✅ |
|
||||||
|
| `insertBefore()` | ✅ |
|
||||||
|
| `removeChild()` | ✅ |
|
||||||
|
| `selectRootElement()` | ✅ |
|
||||||
|
| `parentNode()` | ❌ |
|
||||||
|
| `nextSibling()` | ❌ |
|
||||||
|
| `setAttribute()` | ✅ |
|
||||||
|
| `removeAttribute()` | ✅ |
|
||||||
|
| `addClass()` | ✅ |
|
||||||
|
| `removeClass()` | ✅ |
|
||||||
|
| `setStyle()` | ✅ |
|
||||||
|
| `removeStyle()` | ✅ |
|
||||||
|
| `setProperty()` | ✅ |
|
||||||
|
| `setValue()` | ✅ |
|
||||||
|
| `listen()` | ✅ |
|
|
@ -25,6 +25,8 @@ declare global {
|
||||||
rendererRemoveClass: number;
|
rendererRemoveClass: number;
|
||||||
rendererSetStyle: number;
|
rendererSetStyle: number;
|
||||||
rendererRemoveStyle: number;
|
rendererRemoveStyle: number;
|
||||||
|
rendererDestroy: number;
|
||||||
|
rendererDestroyNode: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +52,8 @@ export const ngDevModeResetPerfCounters: () => void =
|
||||||
rendererRemoveClass: 0,
|
rendererRemoveClass: 0,
|
||||||
rendererSetStyle: 0,
|
rendererSetStyle: 0,
|
||||||
rendererRemoveStyle: 0,
|
rendererRemoveStyle: 0,
|
||||||
|
rendererDestroy: 0,
|
||||||
|
rendererDestroyNode: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
ngDevModeResetPerfCounters();
|
ngDevModeResetPerfCounters();
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
import {assertNotNull} from './assert';
|
import {assertNotNull} from './assert';
|
||||||
import {callHooks} from './hooks';
|
import {callHooks} from './hooks';
|
||||||
import {LContainer, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
|
import {LContainer, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
|
||||||
import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
|
import {LContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
|
||||||
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
|
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
|
||||||
import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
|
import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
|
||||||
import {HookData, LView, LViewOrLContainer, TView, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
import {HookData, LView, LViewOrLContainer, TView, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
||||||
|
@ -215,8 +215,15 @@ export function addRemoveViewFromContainer(
|
||||||
renderer.insertBefore(parent, node.native !, beforeNode as RNode | null) :
|
renderer.insertBefore(parent, node.native !, beforeNode as RNode | null) :
|
||||||
parent.insertBefore(node.native !, beforeNode as RNode | null, true);
|
parent.insertBefore(node.native !, beforeNode as RNode | null, true);
|
||||||
} else {
|
} else {
|
||||||
isProceduralRenderer(renderer) ? renderer.removeChild(parent as RElement, node.native !) :
|
if (isProceduralRenderer(renderer)) {
|
||||||
parent.removeChild(node.native !);
|
renderer.removeChild(parent as RElement, node.native !);
|
||||||
|
if (renderer.destroyNode) {
|
||||||
|
ngDevMode && ngDevMode.rendererDestroyNode++;
|
||||||
|
renderer.destroyNode(node.native !);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parent.removeChild(node.native !);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nextNode = getNextLNode(node);
|
nextNode = getNextLNode(node);
|
||||||
} else if (node.tNode.type === TNodeType.Container) {
|
} else if (node.tNode.type === TNodeType.Container) {
|
||||||
|
@ -398,6 +405,11 @@ function cleanUpView(view: LView): void {
|
||||||
removeListeners(view);
|
removeListeners(view);
|
||||||
executeOnDestroys(view);
|
executeOnDestroys(view);
|
||||||
executePipeOnDestroys(view);
|
executePipeOnDestroys(view);
|
||||||
|
// For component views only, the local renderer is destroyed as clean up time.
|
||||||
|
if (view.id === -1 && isProceduralRenderer(view.renderer)) {
|
||||||
|
ngDevMode && ngDevMode.rendererDestroy++;
|
||||||
|
view.renderer.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Removes listeners and unsubscribes from output subscriptions */
|
/** Removes listeners and unsubscribes from output subscriptions */
|
||||||
|
|
|
@ -53,6 +53,7 @@ export class TemplateFixture extends BaseFixture {
|
||||||
private _directiveDefs: DirectiveDefList|null;
|
private _directiveDefs: DirectiveDefList|null;
|
||||||
private _pipeDefs: PipeDefList|null;
|
private _pipeDefs: PipeDefList|null;
|
||||||
private _sanitizer: Sanitizer|null;
|
private _sanitizer: Sanitizer|null;
|
||||||
|
private _rendererFactory: RendererFactory3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -64,11 +65,12 @@ export class TemplateFixture extends BaseFixture {
|
||||||
constructor(
|
constructor(
|
||||||
private createBlock: () => void, private updateBlock: () => void = noop,
|
private createBlock: () => void, private updateBlock: () => void = noop,
|
||||||
directives?: DirectiveTypesOrFactory|null, pipes?: PipeTypesOrFactory|null,
|
directives?: DirectiveTypesOrFactory|null, pipes?: PipeTypesOrFactory|null,
|
||||||
sanitizer?: Sanitizer) {
|
sanitizer?: Sanitizer|null, rendererFactory?: RendererFactory3) {
|
||||||
super();
|
super();
|
||||||
this._directiveDefs = toDefs(directives, extractDirectiveDef);
|
this._directiveDefs = toDefs(directives, extractDirectiveDef);
|
||||||
this._pipeDefs = toDefs(pipes, extractPipeDef);
|
this._pipeDefs = toDefs(pipes, extractPipeDef);
|
||||||
this._sanitizer = sanitizer || null;
|
this._sanitizer = sanitizer || null;
|
||||||
|
this._rendererFactory = rendererFactory || domRendererFactory3;
|
||||||
this.hostNode = renderTemplate(this.hostElement, (rf: RenderFlags, ctx: any) => {
|
this.hostNode = renderTemplate(this.hostElement, (rf: RenderFlags, ctx: any) => {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
this.createBlock();
|
this.createBlock();
|
||||||
|
@ -76,7 +78,7 @@ export class TemplateFixture extends BaseFixture {
|
||||||
if (rf & RenderFlags.Update) {
|
if (rf & RenderFlags.Update) {
|
||||||
this.updateBlock();
|
this.updateBlock();
|
||||||
}
|
}
|
||||||
}, null !, domRendererFactory3, null, this._directiveDefs, this._pipeDefs, sanitizer);
|
}, null !, this._rendererFactory, null, this._directiveDefs, this._pipeDefs, sanitizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,7 +88,7 @@ export class TemplateFixture extends BaseFixture {
|
||||||
*/
|
*/
|
||||||
update(updateBlock?: () => void): void {
|
update(updateBlock?: () => void): void {
|
||||||
renderTemplate(
|
renderTemplate(
|
||||||
this.hostNode.native, updateBlock || this.updateBlock, null !, domRendererFactory3,
|
this.hostNode.native, updateBlock || this.updateBlock, null !, this._rendererFactory,
|
||||||
this.hostNode, this._directiveDefs, this._pipeDefs, this._sanitizer);
|
this.hostNode, this._directiveDefs, this._pipeDefs, this._sanitizer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,12 @@ import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/brow
|
||||||
|
|
||||||
import {RendererType2, ViewEncapsulation} from '../../src/core';
|
import {RendererType2, ViewEncapsulation} from '../../src/core';
|
||||||
import {defineComponent, detectChanges} from '../../src/render3/index';
|
import {defineComponent, detectChanges} from '../../src/render3/index';
|
||||||
import {bind, elementEnd, elementProperty, elementStart, listener, text, tick} from '../../src/render3/instructions';
|
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, listener, text, tick} from '../../src/render3/instructions';
|
||||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||||
import {createRendererType2} from '../../src/view/index';
|
import {createRendererType2} from '../../src/view/index';
|
||||||
|
|
||||||
import {getAnimationRendererFactory2, getRendererFactory2} from './imported_renderer2';
|
import {getAnimationRendererFactory2, getRendererFactory2} from './imported_renderer2';
|
||||||
import {containerEl, document, renderComponent, renderToHtml, toHtml} from './render_util';
|
import {TemplateFixture, containerEl, document, renderComponent, renderToHtml, toHtml} from './render_util';
|
||||||
|
|
||||||
describe('renderer factory lifecycle', () => {
|
describe('renderer factory lifecycle', () => {
|
||||||
let logs: string[] = [];
|
let logs: string[] = [];
|
||||||
|
@ -206,3 +206,110 @@ describe('animation renderer factory', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Renderer2 destruction hooks', () => {
|
||||||
|
const rendererFactory = getRendererFactory2(document);
|
||||||
|
const origCreateRenderer = rendererFactory.createRenderer;
|
||||||
|
rendererFactory.createRenderer = function() {
|
||||||
|
const renderer = origCreateRenderer.apply(this, arguments);
|
||||||
|
renderer.destroyNode = () => {};
|
||||||
|
return renderer;
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should call renderer.destroyNode for each node destroyed', () => {
|
||||||
|
let condition = true;
|
||||||
|
|
||||||
|
function createTemplate() {
|
||||||
|
elementStart(0, 'div');
|
||||||
|
{ container(1); }
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTemplate() {
|
||||||
|
containerRefreshStart(1);
|
||||||
|
{
|
||||||
|
if (condition) {
|
||||||
|
let rf1 = embeddedViewStart(1);
|
||||||
|
{
|
||||||
|
if (rf1 & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'span');
|
||||||
|
elementEnd();
|
||||||
|
elementStart(1, 'span');
|
||||||
|
elementEnd();
|
||||||
|
elementStart(2, 'span');
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
embeddedViewEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
containerRefreshEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
const t =
|
||||||
|
new TemplateFixture(createTemplate, updateTemplate, null, null, null, rendererFactory);
|
||||||
|
|
||||||
|
expect(t.html).toEqual('<div><span></span><span></span><span></span></div>');
|
||||||
|
|
||||||
|
condition = false;
|
||||||
|
t.update();
|
||||||
|
expect(t.html).toEqual('<div></div>');
|
||||||
|
expect(ngDevMode).toHaveProperties({rendererDestroy: 0, rendererDestroyNode: 3});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call renderer.destroy for each component destroyed', () => {
|
||||||
|
class SimpleComponent {
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: SimpleComponent,
|
||||||
|
selectors: [['simple']],
|
||||||
|
template: function(rf: RenderFlags, ctx: SimpleComponent) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'span');
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
factory: () => new SimpleComponent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let condition = true;
|
||||||
|
|
||||||
|
function createTemplate() {
|
||||||
|
elementStart(0, 'div');
|
||||||
|
{ container(1); }
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTemplate() {
|
||||||
|
containerRefreshStart(1);
|
||||||
|
{
|
||||||
|
if (condition) {
|
||||||
|
let rf1 = embeddedViewStart(1);
|
||||||
|
{
|
||||||
|
if (rf1 & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'simple');
|
||||||
|
elementEnd();
|
||||||
|
elementStart(1, 'span');
|
||||||
|
elementEnd();
|
||||||
|
elementStart(2, 'simple');
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
embeddedViewEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
containerRefreshEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = new TemplateFixture(
|
||||||
|
createTemplate, updateTemplate, [SimpleComponent], null, null, rendererFactory);
|
||||||
|
|
||||||
|
expect(t.html).toEqual(
|
||||||
|
'<div><simple><span></span></simple><span></span><simple><span></span></simple></div>');
|
||||||
|
|
||||||
|
condition = false;
|
||||||
|
t.update();
|
||||||
|
expect(t.html).toEqual('<div></div>');
|
||||||
|
expect(ngDevMode).toHaveProperties({rendererDestroy: 2, rendererDestroyNode: 3});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue