fix(ivy): OnDestroy hook should not be called twice for a directive on an element (#22350)
PR Close #22350
This commit is contained in:
parent
f194d00366
commit
b3ffeaa22b
@ -233,7 +233,9 @@ export function destroyViewTree(rootView: LView): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (next == null) {
|
if (next == null) {
|
||||||
while (viewOrContainer && !viewOrContainer !.next) {
|
// If the viewOrContainer is the rootView, then the cleanup is done twice.
|
||||||
|
// Without this check, ngOnDestroy would be called twice for a directive on an element.
|
||||||
|
while (viewOrContainer && !viewOrContainer !.next && viewOrContainer !== rootView) {
|
||||||
cleanUpView(viewOrContainer as LView);
|
cleanUpView(viewOrContainer as LView);
|
||||||
viewOrContainer = getParentState(viewOrContainer, rootView);
|
viewOrContainer = getParentState(viewOrContainer, rootView);
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,12 @@ describe('lifecycles', () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Directive {
|
||||||
|
ngOnInit() { events.push('dir'); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({type: Directive, factory: () => new Directive()});
|
||||||
|
}
|
||||||
|
|
||||||
it('should call onInit method after inputs are set in creation mode (and not in update mode)',
|
it('should call onInit method after inputs are set in creation mode (and not in update mode)',
|
||||||
() => {
|
() => {
|
||||||
/** <comp [val]="val"></comp> */
|
/** <comp [val]="val"></comp> */
|
||||||
@ -221,12 +227,7 @@ describe('lifecycles', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should be called on directives after component', () => {
|
it('should be called on directives after component', () => {
|
||||||
class Directive {
|
/** <comp directive></comp> */
|
||||||
ngOnInit() { events.push('dir'); }
|
|
||||||
|
|
||||||
static ngDirectiveDef = defineDirective({type: Directive, factory: () => new Directive()});
|
|
||||||
}
|
|
||||||
|
|
||||||
function Template(ctx: any, cm: boolean) {
|
function Template(ctx: any, cm: boolean) {
|
||||||
if (cm) {
|
if (cm) {
|
||||||
elementStart(0, Comp, null, [Directive]);
|
elementStart(0, Comp, null, [Directive]);
|
||||||
@ -246,6 +247,24 @@ describe('lifecycles', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be called on directives on an element', () => {
|
||||||
|
/** <div directive></div> */
|
||||||
|
function Template(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'div', null, [Directive]);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
Directive.ngDirectiveDef.h(1, 0);
|
||||||
|
componentRefresh(1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToHtml(Template, {});
|
||||||
|
expect(events).toEqual(['dir']);
|
||||||
|
|
||||||
|
renderToHtml(Template, {});
|
||||||
|
expect(events).toEqual(['dir']);
|
||||||
|
});
|
||||||
|
|
||||||
it('should call onInit properly in for loop', () => {
|
it('should call onInit properly in for loop', () => {
|
||||||
/**
|
/**
|
||||||
* <comp [val]="1"></comp>
|
* <comp [val]="1"></comp>
|
||||||
@ -370,6 +389,12 @@ describe('lifecycles', () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Directive {
|
||||||
|
ngDoCheck() { events.push('dir'); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({type: Directive, factory: () => new Directive()});
|
||||||
|
}
|
||||||
|
|
||||||
it('should call doCheck on every refresh', () => {
|
it('should call doCheck on every refresh', () => {
|
||||||
/** <comp></comp> */
|
/** <comp></comp> */
|
||||||
function Template(ctx: any, cm: boolean) {
|
function Template(ctx: any, cm: boolean) {
|
||||||
@ -425,6 +450,45 @@ describe('lifecycles', () => {
|
|||||||
expect(allEvents).toEqual(['init comp', 'check comp', 'check comp']);
|
expect(allEvents).toEqual(['init comp', 'check comp', 'check comp']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be called on directives after component', () => {
|
||||||
|
/** <comp directive></comp> */
|
||||||
|
function Template(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, Comp, null, [Directive]);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
Comp.ngComponentDef.h(1, 0);
|
||||||
|
Directive.ngDirectiveDef.h(2, 0);
|
||||||
|
componentRefresh(1, 0);
|
||||||
|
componentRefresh(2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToHtml(Template, {});
|
||||||
|
expect(events).toEqual(['comp', 'dir']);
|
||||||
|
|
||||||
|
renderToHtml(Template, {});
|
||||||
|
expect(events).toEqual(['comp', 'dir', 'comp', 'dir']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be called on directives on an element', () => {
|
||||||
|
/** <div directive></div> */
|
||||||
|
function Template(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
elementStart(0, 'div', null, [Directive]);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
Directive.ngDirectiveDef.h(1, 0);
|
||||||
|
componentRefresh(1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToHtml(Template, {});
|
||||||
|
expect(events).toEqual(['dir']);
|
||||||
|
|
||||||
|
renderToHtml(Template, {});
|
||||||
|
expect(events).toEqual(['dir', 'dir']);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('afterContentInit', () => {
|
describe('afterContentInit', () => {
|
||||||
@ -1324,6 +1388,12 @@ describe('lifecycles', () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Directive {
|
||||||
|
ngOnDestroy() { events.push('dir'); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({type: Directive, factory: () => new Directive()});
|
||||||
|
}
|
||||||
|
|
||||||
it('should call destroy when view is removed', () => {
|
it('should call destroy when view is removed', () => {
|
||||||
/**
|
/**
|
||||||
* % if (condition) {
|
* % if (condition) {
|
||||||
@ -1747,6 +1817,74 @@ describe('lifecycles', () => {
|
|||||||
expect(ctx.counter).toEqual(2);
|
expect(ctx.counter).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be called on directives after component', () => {
|
||||||
|
/**
|
||||||
|
* % if (condition) {
|
||||||
|
* <comp></comp>
|
||||||
|
* % }
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Template(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
container(0);
|
||||||
|
}
|
||||||
|
containerRefreshStart(0);
|
||||||
|
{
|
||||||
|
if (ctx.condition) {
|
||||||
|
if (embeddedViewStart(0)) {
|
||||||
|
elementStart(0, Comp, null, [Directive]);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
Comp.ngComponentDef.h(1, 0);
|
||||||
|
Directive.ngDirectiveDef.h(2, 0);
|
||||||
|
componentRefresh(1, 0);
|
||||||
|
componentRefresh(2, 0);
|
||||||
|
embeddedViewEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
containerRefreshEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToHtml(Template, {condition: true});
|
||||||
|
expect(events).toEqual([]);
|
||||||
|
|
||||||
|
renderToHtml(Template, {condition: false});
|
||||||
|
expect(events).toEqual(['comp', 'dir']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be called on directives on an element', () => {
|
||||||
|
/**
|
||||||
|
* % if (condition) {
|
||||||
|
* <div directive></div>
|
||||||
|
* % }
|
||||||
|
*/
|
||||||
|
|
||||||
|
function Template(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
container(0);
|
||||||
|
}
|
||||||
|
containerRefreshStart(0);
|
||||||
|
{
|
||||||
|
if (ctx.condition) {
|
||||||
|
if (embeddedViewStart(0)) {
|
||||||
|
elementStart(0, 'div', null, [Directive]);
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
Directive.ngDirectiveDef.h(1, 0);
|
||||||
|
componentRefresh(1, 0);
|
||||||
|
embeddedViewEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
containerRefreshEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToHtml(Template, {condition: true});
|
||||||
|
expect(events).toEqual([]);
|
||||||
|
|
||||||
|
renderToHtml(Template, {condition: false});
|
||||||
|
expect(events).toEqual(['dir']);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user