fix(ivy): OnDestroy hook should not be called twice for a directive on an element (#22350)

PR Close #22350
This commit is contained in:
Marc Laval 2018-02-21 15:08:18 +01:00 committed by Victor Berchet
parent f194d00366
commit b3ffeaa22b
2 changed files with 147 additions and 7 deletions

View File

@ -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);
} }

View File

@ -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']);
});
}); });