2017-06-29 164 views
5

我有点困惑,为什么ngAfterContentInit在这种情况下执行两次。我创建了一个简化版本的应用程序来重现错误。简而言之,我使用*contentItem来标记组件,然后由standard-layout组件进行渲染。只要我遵循这种模式,demongAfterContentInit被执行两次。停止ngAfterContentInit执行两次

我放在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"); 
    } 
} 

标准布局.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; 
} 
//////////////////////////////////////////////////////////////////////////////// 

和最后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); 
} 

当我运行一次此,Hello World打印效果与预期: enter image description here

但在终端看时:

enter image description here

所以demo组件只呈现一次,但其ngAfterContentInit正在执行两次,当您的假设是它只被执行一次时会造成严重破坏。

我已经尝试添加哈克解决方法,但似乎组件实际上得到重新呈现两次:

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好措施:

<!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> 

更新

于是有人认为,可能是因为它只会发生两次在开发模式。我已经完成了pub build并运行了index.html,它现在在常规Chrome中包含main.dart.js

enter image description here

它仍然执行两次,试图用ngAfterViewInit而那也是执行两次。

登录在此期间的错误: https://github.com/dart-lang/angular/issues/478

+1

如果您使用'ngFor'为子组件迭代,则不能将被调用的'ngAfterInit()'限制为一个确定的限制。 – Aravind

+0

好的,能够呈现多个受限制的子组件并且只获得一个'ngAfterContentInit'调用的解决方法是什么? –

+0

你想达到什么目的。我很困惑。请详细说明 – Aravind

回答

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: Demo 516575718

ngAfterContentInit: Demo 57032191

这意味着两个不同演示实例是活着的,不只是一个发射两次。然后我加入StackTrace.current到演示的构造函数来得到一个堆栈跟踪:

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

,并得到:

0 Demo.Demo (package:bugdemo/demo.dart:11:20) 1 ViewBuggyApp1.build (package:bugdemo/buggy-app.template.dart:136:21) 2 AppView.create (package:angular2/src/core/linker/app_view.dart:180:12) 3 DebugAppView.create (package:angular2/src/debug/debug_app_view.dart:73:26) 4 TemplateRef.createEmbeddedView (package:angular2/src/core/linker/template_ref.dart:27:10) 5 ViewContainer.createEmbeddedView (package: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。我想你不打算这样做。所以我想知道你在做什么,并通过在这些地方之一中删除创建组件来解决你的问题。

2

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

你是在开发模式?控制台告诉你什么时候应用程序引导。在开发模式下,Angular执行两次changeDetection,因此它可以检测副作用。

+0

你是否建议它只在开发模式下运行两次?我现在使用'pub build'完成了一个生产版本,看起来它也在生产中。 –

+1

它可能是,但如果这发生在产品模式...我不知道:/ –

+1

AFAIK在DART没有devmode其中变化检测运行两次,但我不是绝对确定。 –