停止ngAfterContentInit执行两次

时间:2017-06-29 18:12:11

标签: angular dart angular-dart

我有点困惑为什么ngAfterContentInit在这种情况下执行两次。我已经创建了我们的应用程序的精简版本来重现该错误。简而言之,我使用*contentItem来标记组件,然后由standard-layout组件选取这些组件进行渲染。只要我遵循此模式,demo的{​​{1}}就会被执行两次。

我将演示应用程序放在github上,这将重现错误: https://github.com/jVaaS/stackoverflow/tree/master/ngaftercontentinit

否则以下是重点:

车-app.dart:

ngAfterContentInit

标准layout.dart:

@Component(
    selector: "buggy-app",
    template: """       
        <standard-layout>
            <demo *contentItem></demo>
        </standard-layout>
    """,
    directives: const [
        ContentItemDirective,
        StandardLayout,
        Demo
    ]
)
class BuggyApp implements AfterContentInit {

    @override
    ngAfterContentInit() {
        print(">>> ngAfterContentInit: BuggyApp");
    }
}

最后//////////////////////////////////////////////////////////////////////////////// /// /// Standard Layout Component /// <standard-layout></standard-layout> /// @Component( selector: "standard-layout", template: """ <div *ngFor="let item of contentItems ?? []"> <template [ngTemplateOutlet]="item.template"></template> </div> """, directives: const [ROUTER_DIRECTIVES, ContentItem]) class StandardLayout implements AfterContentInit { @ContentChildren(ContentItemDirective) QueryList<ContentItemDirective> contentItems; @override ngAfterContentInit() { print(">>> ngAfterContentInit: StandardLayout"); } } //////////////////////////////////////////////////////////////////////////////// /// /// Content Item Directive /// *contentItem /// @Directive(selector: '[contentItem]') class ContentItemDirective implements AfterContentInit { final ViewContainerRef vcRef; final TemplateRef template; final ComponentResolver componentResolver; ContentItemDirective(this.vcRef, this.template, this.componentResolver); ComponentRef componentRef; @override ngAfterContentInit() async { final componentFactory = await componentResolver.resolveComponent(ContentItem); componentRef = vcRef.createComponent(componentFactory); (componentRef.instance as ContentItem) ..template = template; } } //////////////////////////////////////////////////////////////////////////////// /// /// Content Item Generator /// @Component( selector: "content-item", host: const { '[class.content-item]': "true", }, template: """ <template [ngTemplateOutlet]="template"></template> """, directives: const [NgTemplateOutlet] ) class ContentItem { TemplateRef template; } ////////////////////////////////////////////////////////////////////////////////

demo.dart

@Component( selector: "demo", template: "Hello World Once, but demo prints twice!") class Demo implements AfterContentInit { @override ngAfterContentInit() { print(">>> ngAfterContentInit: Demo"); } } 内容不多:

main.dart

运行时,void main() { bootstrap(BuggyApp); } 按预期打印一次: enter image description here

但是在看终端时:

enter image description here

所以Hello World组件只渲染一次,但它的demo正在被执行两次,当你的假设是它只被执行一次时会造成破坏。

我尝试添加一个hacky解决方法,但似乎组件实际上重新渲染了两次:

ngAfterContentInit

这是Angular中的错误还是我可以做些什么来防止这种情况?

如果需要,请

int counter = 0; @override ngAfterContentInit() { if (counter == 0) { print(">>> ngAfterContentInit: Demo"); counter++; } }

pubspec.yaml

name: bugdemo version: 0.0.0 description: Bug Demo environment: sdk: '>=1.13.0 <2.0.0' dependencies: angular2: 3.1.0 browser: ^0.10.0 http: any js: ^0.6.0 dart_to_js_script_rewriter: ^1.0.1 transformers: - angular2: platform_directives: - package:angular2/common.dart#COMMON_DIRECTIVES platform_pipes: - package:angular2/common.dart#COMMON_PIPES entry_points: web/main.dart - dart_to_js_script_rewriter - $dart2js: commandLineOptions: [--enable-experimental-mirrors] 表示良好的衡量标准:

index.html

更新

所以有人建议它可能只在dev模式下发生两次。我已经完成<!DOCTYPE html> <html> <head> <title>Strange Bug</title> </head> <body> <buggy-app> Loading ... </buggy-app> <script async src="main.dart" type="application/dart"></script> <script async src="packages/browser/dart.js"></script> </body> </html> 并在常规Chrome中运行pub build现在包含index.html

enter image description here

它仍在执行两次,尝试使用main.dart.js并且执行两次。

在此期间记录了一个错误: https://github.com/dart-lang/angular/issues/478

2 个答案:

答案 0 :(得分:3)

这似乎不是一个错误。

以下是demo.dart生成的代码: https://gist.github.com/matanlurey/f311d90053e36cc09c6e28f28ab2d4cd

void detectChangesInternal() {
  bool firstCheck = identical(this.cdState, ChangeDetectorState.NeverChecked);
  final _ctx = ctx;
  if (!import8.AppViewUtils.throwOnChanges) {
    dbg(0, 0, 0);
    if (firstCheck) {
      _Demo_0_2.ngAfterContentInit();
    }
  }
  _compView_0.detectChanges();
}

我很怀疑,因此我更改了您的打印消息以包含hashCode

@override
ngAfterContentInit() {
  print(">>> ngAfterContentInit: Demo $hashCode");
}

然后我看到了以下内容:

  

ngAfterContentInit:演示516575718

     

ngAfterContentInit:演示57032191

这意味着两个不同的demo实例是活着的,而不只是一个两次发射。然后我将StackTrace.current添加到demo的构造函数中以获取堆栈跟踪:

Demo() {
  print('Created $this:$hashCode');
  print(StackTrace.current);
}

得到了:

  

0 Demo.Demo(包:bugdemo / demo.dart:11:20)   1 ViewBuggyApp1.build(包:bugdemo / buggy-app.template.dart:136:21)   2 AppView.create(包:angular2 / src / core / linker / app_view.dart:180:12)   3 DebugAppView.create(包:angular2 / src / debug / debug_app_view.dart:73:26)   4 TemplateRef.createEmbeddedView(包:angular2 / src / core / linker / template_ref.dart:27:10)   5 ViewContainer.createEmbeddedView(包:angular2 / src / core / linker / view_container.dart:86:43)

反过来说,你的组件是由BuggyApp的模板创建的:

<standard-layout>
  <demo *contentItem></demo>
</standard-layout>

更紧密。挖掘。

我评论了ContentItemDirective,看到Demo现在创建了一次。我想你会期望它不会因为*contentItem而被创建,所以不断挖掘 - 最后想出来了。

您的StandardLayout组件会创建模板:

<div *ngFor="let item of contentItems ?? []">
  <template [ngTemplateOutlet]="item.template"></template>
</div>

ContentItemDirective来自 ComponentResolver 也是如此。我想你不是故意的。因此,我会通过删除在其中一个位置创建组件来弄清楚您要尝试做什么并解决您的问题。

答案 1 :(得分:2)

可能与In Angular2, why are there 2 times check for content and view after setTimeout?

有关

您是否处于开发模式?控制台告诉您应用程序引导程序的时间。在开发模式下,Angular会对changeDetection执行两次,因此它可以检测到副作用。