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
|
||||
| Feature | Runtime | Spec | Compiler |
|
||||
| ----------------------------------- | ------- | -------- | -------- |
|
||||
| Render3.None | ✅ | ✅ | ✅ |
|
||||
| Render2.None | ✅ | ✅ | ✅ |
|
||||
| Render2.Emulated | ❌ | ❌ | ❌ |
|
||||
| Render2.Native | ❌ | ❌ | ❌ |
|
||||
| Renderer3.None | ✅ | ✅ | ✅ |
|
||||
| Renderer2.None | ✅ | ✅ | ✅ |
|
||||
| Renderer2.Emulated | ❌ | ❌ | ❌ |
|
||||
| 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 | ✅ |
|
||||
| `reattach()` | 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;
|
||||
rendererSetStyle: number;
|
||||
rendererRemoveStyle: number;
|
||||
rendererDestroy: number;
|
||||
rendererDestroyNode: number;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,6 +52,8 @@ export const ngDevModeResetPerfCounters: () => void =
|
|||
rendererRemoveClass: 0,
|
||||
rendererSetStyle: 0,
|
||||
rendererRemoveStyle: 0,
|
||||
rendererDestroy: 0,
|
||||
rendererDestroyNode: 0,
|
||||
};
|
||||
}
|
||||
ngDevModeResetPerfCounters();
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import {assertNotNull} from './assert';
|
||||
import {callHooks} from './hooks';
|
||||
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 {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
|
||||
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) :
|
||||
parent.insertBefore(node.native !, beforeNode as RNode | null, true);
|
||||
} else {
|
||||
isProceduralRenderer(renderer) ? renderer.removeChild(parent as RElement, node.native !) :
|
||||
parent.removeChild(node.native !);
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
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);
|
||||
} else if (node.tNode.type === TNodeType.Container) {
|
||||
|
@ -398,6 +405,11 @@ 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.id === -1 && isProceduralRenderer(view.renderer)) {
|
||||
ngDevMode && ngDevMode.rendererDestroy++;
|
||||
view.renderer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes listeners and unsubscribes from output subscriptions */
|
||||
|
|
|
@ -53,6 +53,7 @@ export class TemplateFixture extends BaseFixture {
|
|||
private _directiveDefs: DirectiveDefList|null;
|
||||
private _pipeDefs: PipeDefList|null;
|
||||
private _sanitizer: Sanitizer|null;
|
||||
private _rendererFactory: RendererFactory3;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -64,11 +65,12 @@ export class TemplateFixture extends BaseFixture {
|
|||
constructor(
|
||||
private createBlock: () => void, private updateBlock: () => void = noop,
|
||||
directives?: DirectiveTypesOrFactory|null, pipes?: PipeTypesOrFactory|null,
|
||||
sanitizer?: Sanitizer) {
|
||||
sanitizer?: Sanitizer|null, rendererFactory?: RendererFactory3) {
|
||||
super();
|
||||
this._directiveDefs = toDefs(directives, extractDirectiveDef);
|
||||
this._pipeDefs = toDefs(pipes, extractPipeDef);
|
||||
this._sanitizer = sanitizer || null;
|
||||
this._rendererFactory = rendererFactory || domRendererFactory3;
|
||||
this.hostNode = renderTemplate(this.hostElement, (rf: RenderFlags, ctx: any) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
this.createBlock();
|
||||
|
@ -76,7 +78,7 @@ export class TemplateFixture extends BaseFixture {
|
|||
if (rf & RenderFlags.Update) {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,12 @@ import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/brow
|
|||
|
||||
import {RendererType2, ViewEncapsulation} from '../../src/core';
|
||||
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 {createRendererType2} from '../../src/view/index';
|
||||
|
||||
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', () => {
|
||||
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