停止执行两次ngAfterContentInit

Jan*_*ert 5 dart angular-dart angular

我有点困惑为什么ngAfterContentInit在这种情况下执行两次.我已经创建了我们的应用程序的精简版本来重现该错误.简而言之,我使用a *contentItem来标记组件,然后由standard-layout组件拾取以进行渲染.只要我遵循这个模式,demongAfterContentInit执行两次.

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

否则这里是重要的一点:

越野车,app.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");
    }
}
Run Code Online (Sandbox Code Playgroud)

标准layout.dart:

////////////////////////////////////////////////////////////////////////////////
///
/// 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;
}
////////////////////////////////////////////////////////////////////////////////
Run Code Online (Sandbox Code Playgroud)

最后demo.dart:

@Component(
    selector: "demo",
    template: "Hello World Once, but demo prints twice!")
class Demo implements AfterContentInit {

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

}
Run Code Online (Sandbox Code Playgroud)

main.dart 其中没有多少:

void main() {
    bootstrap(BuggyApp);
}
Run Code Online (Sandbox Code Playgroud)

当我运行它时,Hello World按预期打印一次: 在此输入图像描述

但是看着终端时:

在此输入图像描述

所以demo组件只渲染了一次,但它ngAfterContentInit被执行了两次,当你的假设是它只被执行一次时会造成破坏.

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

int counter = 0;

@override
ngAfterContentInit() {

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

}
Run Code Online (Sandbox Code Playgroud)

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

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]
Run Code Online (Sandbox Code Playgroud)

并且index.html衡量标准:

<!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>
Run Code Online (Sandbox Code Playgroud)

更新

所以有人建议它可能只在dev模式下发生两次.我已经完成pub build并运行了index.html现在包含main.dart.js在常规Chrome中的内容.

在此输入图像描述

它仍然执行两次,尝试过,ngAfterViewInit并且执行两次.

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

mat*_*rey 3

这看起来不像是一个错误。

这是生成的代码demo.darthttps://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();
}
Run Code Online (Sandbox Code Playgroud)

我很怀疑,所以我更改了您的打印消息以包括hashCode

@override
ngAfterContentInit() {
  print(">>> ngAfterContentInit: Demo $hashCode");
}
Run Code Online (Sandbox Code Playgroud)

然后我看到了以下内容:

ngAfterContentInit:演示 516575718

ngAfterContentInit:演示 57032191

这意味着演示的两个不同实例都处于活动状态,而不仅仅是一个发出两次信号。然后我添加StackTrace.current到演示的构造函数中以获取堆栈跟踪:

Demo() {
  print('Created $this:$hashCode');
  print(StackTrace.current);
}
Run Code Online (Sandbox Code Playgroud)

并得到:

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>
Run Code Online (Sandbox Code Playgroud)

更近一些。继续挖掘。

我注释掉了ContentItemDirective,看到Demo现在已经创建了一次。我想你预计它根本不会被创建*contentItem,所以继续挖掘 - 并最终弄清楚了。

您的StandardLayout组件创建模板:

<div *ngFor="let item of contentItems ?? []">
  <template [ngTemplateOutlet]="item.template"></template>
</div>
Run Code Online (Sandbox Code Playgroud)

ContentItemDirective但你的via ComponentResolver也是如此。我想你并不是故意的。因此,我会弄清楚您想要做什么,并通过删除在这些位置之一创建组件来解决您的问题。