Skip to content
This repository has been archived by the owner on Sep 16, 2022. It is now read-only.

fix(Compiler): Fix zombie nodes of template->deferred #1562

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 37 additions & 14 deletions _goldens/test/_files/deferred/container_component.template.golden
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@ final List<dynamic> styles$TestContainerComponent = const [];

class ViewTestContainerComponent0 extends AppView<import1.TestContainerComponent> {
ViewContainer _appEl_0;
void Function() _cancelDeferredLoad0;
ViewContainer _appEl_1;
NgIf _NgIf_1_9;
ViewContainer _appEl_2;
bool _query_queryMe_1_0_isDirty = true;
NgIf _NgIf_2_9;
ViewContainer _appEl_3;
void Function() _cancelDeferredLoad3;
ViewContainer _appEl_4;
void Function() _cancelDeferredLoad4;
import4.Element _el_5;
import5.ViewNotDeferredChildComponent0 _compView_5;
import6.NotDeferredChildComponent _NotDeferredChildComponent_5_5;
Expand All @@ -54,8 +57,8 @@ class ViewTestContainerComponent0 extends AppView<import1.TestContainerComponent
final _anchor_0 = createViewContainerAnchor();
parentRenderNode.append(_anchor_0);
_appEl_0 = ViewContainer(0, null, this, _anchor_0);
TemplateRef _TemplateRef_0_7 = TemplateRef(_appEl_0, viewFactory_TestContainerComponent1);
loadDeferred(deflib0.loadLibrary, deflib1.loadLibrary, _appEl_0, _TemplateRef_0_7);
TemplateRef _TemplateRef_0_8 = TemplateRef(_appEl_0, viewFactory_TestContainerComponent1);
_cancelDeferredLoad0 = loadDeferred(deflib0.loadLibrary, deflib1.loadLibrary, _appEl_0, _TemplateRef_0_8);
final _anchor_1 = createViewContainerAnchor();
parentRenderNode.append(_anchor_1);
_appEl_1 = ViewContainer(1, null, this, _anchor_1);
Expand All @@ -69,13 +72,13 @@ class ViewTestContainerComponent0 extends AppView<import1.TestContainerComponent
final _anchor_3 = createViewContainerAnchor();
parentRenderNode.append(_anchor_3);
_appEl_3 = ViewContainer(3, null, this, _anchor_3);
TemplateRef _TemplateRef_3_7 = TemplateRef(_appEl_3, viewFactory_TestContainerComponent6);
loadDeferred(deflib0.loadLibrary, deflib1.loadLibrary, _appEl_3, _TemplateRef_3_7);
TemplateRef _TemplateRef_3_8 = TemplateRef(_appEl_3, viewFactory_TestContainerComponent6);
_cancelDeferredLoad3 = loadDeferred(deflib0.loadLibrary, deflib1.loadLibrary, _appEl_3, _TemplateRef_3_8);
final _anchor_4 = createViewContainerAnchor();
parentRenderNode.append(_anchor_4);
_appEl_4 = ViewContainer(4, null, this, _anchor_4);
TemplateRef _TemplateRef_4_7 = TemplateRef(_appEl_4, viewFactory_TestContainerComponent7);
loadDeferred(deflib0.loadLibrary, deflib1.loadLibrary, _appEl_4, _TemplateRef_4_7);
TemplateRef _TemplateRef_4_8 = TemplateRef(_appEl_4, viewFactory_TestContainerComponent7);
_cancelDeferredLoad4 = loadDeferred(deflib0.loadLibrary, deflib1.loadLibrary, _appEl_4, _TemplateRef_4_8);
_compView_5 = import5.ViewNotDeferredChildComponent0(this, 5);
_el_5 = _compView_5.rootEl;
parentRenderNode.append(_el_5);
Expand All @@ -90,8 +93,11 @@ class ViewTestContainerComponent0 extends AppView<import1.TestContainerComponent
final import1.TestContainerComponent _ctx = ctx;
_NgIf_1_9.ngIf = _ctx.showDeferredChild;
_NgIf_2_9.ngIf = _ctx.showDeferredChild;
_appEl_0.detectChangesInNestedViews();
_appEl_1.detectChangesInNestedViews();
_appEl_2.detectChangesInNestedViews();
_appEl_3.detectChangesInNestedViews();
_appEl_4.detectChangesInNestedViews();
if (!import10.AppViewUtils.throwOnChanges) {
if (_query_queryMe_1_0_isDirty) {
ctx.queryDeferredChild = import10.firstOrNull(_appEl_2.mapNestedViews((_ViewTestContainerComponent4 nestedView) {
Expand All @@ -102,17 +108,20 @@ class ViewTestContainerComponent0 extends AppView<import1.TestContainerComponent
_query_queryMe_1_0_isDirty = false;
}
}
_appEl_0.detectChangesInNestedViews();
_appEl_3.detectChangesInNestedViews();
_appEl_4.detectChangesInNestedViews();
_compView_5.detectChanges();
}

@override
void destroyInternal() {
_appEl_0?.destroyNestedViews();
_appEl_1?.destroyNestedViews();
_appEl_2?.destroyNestedViews();
_appEl_3?.destroyNestedViews();
_appEl_4?.destroyNestedViews();
_compView_5?.destroy();
_cancelDeferredLoad0();
_cancelDeferredLoad3();
_cancelDeferredLoad4();
}
}

Expand Down Expand Up @@ -154,23 +163,30 @@ AppView<import1.TestContainerComponent> viewFactory_TestContainerComponent1(AppV

class _ViewTestContainerComponent2 extends AppView<import1.TestContainerComponent> {
ViewContainer _appEl_0;
void Function() _cancelDeferredLoad0;
_ViewTestContainerComponent2(AppView<dynamic> parentView, int parentIndex) : super(import8.ViewType.embedded, {}, parentView, parentIndex, ChangeDetectionStrategy.CheckAlways) {
componentType = ViewTestContainerComponent0._renderType;
}
@override
ComponentRef<import1.TestContainerComponent> build() {
final _anchor_0 = createViewContainerAnchor();
_appEl_0 = ViewContainer(0, null, this, _anchor_0);
TemplateRef _TemplateRef_0_7 = TemplateRef(_appEl_0, viewFactory_TestContainerComponent3);
loadDeferred(deflib0.loadLibrary, deflib1.loadLibrary, _appEl_0, _TemplateRef_0_7);
init0(_anchor_0);
TemplateRef _TemplateRef_0_8 = TemplateRef(_appEl_0, viewFactory_TestContainerComponent3);
_cancelDeferredLoad0 = loadDeferred(deflib0.loadLibrary, deflib1.loadLibrary, _appEl_0, _TemplateRef_0_8);
init0(_appEl_0);
return null;
}

@override
void detectChangesInternal() {
_appEl_0.detectChangesInNestedViews();
}

@override
void destroyInternal() {
_appEl_0?.destroyNestedViews();
_cancelDeferredLoad0();
}
}

AppView<import1.TestContainerComponent> viewFactory_TestContainerComponent2(AppView<dynamic> parentView, int parentIndex) {
Expand Down Expand Up @@ -212,6 +228,7 @@ AppView<import1.TestContainerComponent> viewFactory_TestContainerComponent3(AppV
class _ViewTestContainerComponent4 extends AppView<import1.TestContainerComponent> {
import4.DivElement _el_0;
ViewContainer _appEl_1;
void Function() _cancelDeferredLoad1;
_ViewTestContainerComponent4(AppView<dynamic> parentView, int parentIndex) : super(import8.ViewType.embedded, {}, parentView, parentIndex, ChangeDetectionStrategy.CheckAlways) {
componentType = ViewTestContainerComponent0._renderType;
}
Expand All @@ -222,8 +239,8 @@ class _ViewTestContainerComponent4 extends AppView<import1.TestContainerComponen
final _anchor_1 = createViewContainerAnchor();
_el_0.append(_anchor_1);
_appEl_1 = ViewContainer(1, 0, this, _anchor_1);
TemplateRef _TemplateRef_1_7 = TemplateRef(_appEl_1, viewFactory_TestContainerComponent5);
loadDeferred(deflib0.loadLibrary, deflib1.loadLibrary, _appEl_1, _TemplateRef_1_7);
TemplateRef _TemplateRef_1_8 = TemplateRef(_appEl_1, viewFactory_TestContainerComponent5);
_cancelDeferredLoad1 = loadDeferred(deflib0.loadLibrary, deflib1.loadLibrary, _appEl_1, _TemplateRef_1_8);
init0(_el_0);
return null;
}
Expand All @@ -232,6 +249,12 @@ class _ViewTestContainerComponent4 extends AppView<import1.TestContainerComponen
void detectChangesInternal() {
_appEl_1.detectChangesInNestedViews();
}

@override
void destroyInternal() {
_appEl_1?.destroyNestedViews();
_cancelDeferredLoad1();
}
}

AppView<import1.TestContainerComponent> viewFactory_TestContainerComponent4(AppView<dynamic> parentView, int parentIndex) {
Expand Down
233 changes: 233 additions & 0 deletions _tests/test/regression/1539_nested_template_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
@TestOn('browser')
import 'dart:async';

import 'package:angular/angular.dart';
import 'package:angular_test/angular_test.dart';
import 'package:test/test.dart';

import '1539_nested_template_test.template.dart' as ng;

void main() {
tearDown(disposeAnyRunningTest);

test('should render a nested template', () async {
final fixture = await NgTestBed.forComponent(
ng.NestedTemplateTestNgFactory,
).create();

Future<void> setInnerCondition(bool value) {
return fixture.update((c) => c.showInner = value);
}

Future<void> setOuterCondition(bool value) {
return fixture.update((c) => c.showOuter = value);
}

void expectHelloWorld() {
expect(fixture.text, contains('Hello World'));
}

void expectEmpty() {
expect(fixture.text, isNot(contains('Hello World')));
}

expectEmpty();
await setOuterCondition(true);
expectEmpty();
await setInnerCondition(true);
expectHelloWorld();
await setOuterCondition(false);
expectEmpty();
});

test('should render a nested deferred template', () async {
final fixture = await NgTestBed.forComponent(
ng.NestedDeferredTestNgFactory,
).create();

Future<void> setOuterCondition(bool value) {
return fixture.update((c) => c.showOuter = value);
}

void expectHelloWorld() {
expect(fixture.text, contains('Hello World'));
}

void expectEmpty() {
expect(fixture.text, isNot(contains('Hello World')));
}

expectEmpty();
await setOuterCondition(true);
expectHelloWorld();

// This fails prior to fixing #1539, the inner component is not removed.
await setOuterCondition(false);
expectEmpty();
});

test('should render a nested deferred template with a div', () async {
final fixture = await NgTestBed.forComponent(
ng.NestedDeferredWithDivTestNgFactory,
).create();

Future<void> setOuterCondition(bool value) {
return fixture.update((c) => c.showOuter = value);
}

void expectHelloWorld() {
expect(fixture.text, contains('Hello World'));
}

void expectEmpty() {
expect(fixture.text, isNot(contains('Hello World')));
}

expectEmpty();
await setOuterCondition(true);
expectHelloWorld();
await setOuterCondition(false);
expectEmpty();
});

test('should render a nested template with a custom directive', () async {
final fixture = await NgTestBed.forComponent(
ng.NestedCustomTestNgFactory,
).create();

Future<void> setInnerCondition(bool value) {
return fixture.update((c) {
if (value) {
c.showInner.createChildView();
} else {
c.showInner.destroyChildView();
}
});
}

Future<void> setOuterCondition(bool value) {
return fixture.update((c) {
if (value) {
c.showOuter.createChildView();
} else {
c.showOuter.destroyChildView();
}
});
}

void expectHelloWorld() {
expect(fixture.text, contains('Hello World'));
}

void expectEmpty() {
expect(fixture.text, isNot(contains('Hello World')));
}

expectEmpty();
await setOuterCondition(true);
expectEmpty();
await setInnerCondition(true);
expectHelloWorld();
await setOuterCondition(false);
expectEmpty();
});
}

@Component(
selector: 'nested-template-test',
directives: [
HelloWorldComponent,
NgIf,
],
template: r'''
<template [ngIf]="true">
<template [ngIf]="showOuter">
<template [ngIf]="showInner">
<hello-world></hello-world>
</template>
</template>
</template>
''',
)
class NestedTemplateTest {
bool showOuter = false;
bool showInner = false;
}

@Component(
selector: 'nested-deferred-test',
directives: [
HelloWorldComponent,
NgIf,
],
template: r'''
<div *ngIf="showOuter">
<hello-world @deferred></hello-world>
</div>
''',
)
class NestedDeferredWithDivTest {
bool showOuter = false;
}

@Component(
selector: 'nested-deferred-test',
directives: [
HelloWorldComponent,
NgIf,
],
template: r'''
<template [ngIf]="showOuter">
<hello-world @deferred></hello-world>
</template>
''',
)
class NestedDeferredTest {
bool showOuter = false;
}

@Directive(
selector: '[customIf]',
)
class CustomIfDirective {
final TemplateRef _templateRef;
final ViewContainerRef _viewContainer;

CustomIfDirective(this._viewContainer, this._templateRef);

void createChildView() {
_viewContainer.createEmbeddedView(_templateRef);
}

void destroyChildView() {
_viewContainer.clear();
}
}

@Component(
selector: 'nested-custom-test',
directives: [
CustomIfDirective,
HelloWorldComponent,
],
template: r'''
<template customIf #showOuter>
<template customIf #showInner>
<hello-world></hello-world>
</template>
</template>
''',
)
class NestedCustomTest {
@ViewChild('showOuter', read: CustomIfDirective)
CustomIfDirective showOuter;

@ViewChild('showInner', read: CustomIfDirective)
CustomIfDirective showInner;
}

@Component(
selector: 'hello-world',
template: 'Hello World',
)
class HelloWorldComponent {}
10 changes: 9 additions & 1 deletion angular/lib/src/compiler/template_ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,15 @@ class EmbeddedTemplateAst implements TemplateAst {
this.sourceSpan,
{this.hasDeferredComponent = false});

bool get hasViewContainer => elementProviderUsage.requiresViewContainer;
bool get hasViewContainer =>
elementProviderUsage.requiresViewContainer ||
// The view compiler tries to optimize when it sees a `<template>` tag
// without `ViewContainerRef` being accessed (i.e. either via a query or
// via injection in a directive). This behaves badly with `@deferred`,
// which is more or less a compiler-based directive.
//
// See https://github.com/dart-lang/angular/issues/1539.
hasDeferredComponent;

R visit<R, C>(TemplateAstVisitor<R, C> visitor, C context) =>
visitor.visitEmbeddedTemplate(this, context);
Expand Down
Loading