2017-06-29 142 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其中變化檢測運行兩次,但我不是絕對確定。 –