Stop ngAfterContentInit from execution twice

I am a little confused why ngAfterContentInit executes twice in this scenario. I created a stripped down version of our application to reproduce the error. In short, I use *contentItem for component tags, which are then processed by the standard-layout component for rendering. As soon as I follow this pattern, demo ngAfterContentInit is executed twice.

I posted a demo application on github that will reproduce the error: https://github.com/jVaaS/stackoverflow/tree/master/ngaftercontentinit

Otherwise, these are the important bits:

buggy-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"); } } 

Standard-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; } //////////////////////////////////////////////////////////////////////////////// 

and finally demo.dart :

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

main.dart there is not much in it:

 void main() { bootstrap(BuggyApp); } 

When I ran this, Hello World prints once as expected: enter image description here

but when viewing the terminal:

enter image description here

Thus, the demo component displays exactly once, but its ngAfterContentInit runs twice, which causes chaos when your assumption is that it runs only once.

I tried adding a hacky workaround, but it seems that the component does receive the re-render twice:

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

Is this a bug in Angular or is there something I can do to prevent this?

pubspec.yaml if necessary:

 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] 

and index.html for a good measure:

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

Update

Therefore, it has been suggested that this may be the case that this only happens twice in dev-mode. I did pub build and ran index.html , which now contains main.dart.js in regular Chrome.

enter image description here

It still runs twice, tried using ngAfterViewInit , and this also runs twice.

Logged Error: https://github.com/dart-lang/angular/issues/478

+5
source share
2 answers

This is not like a mistake.

Here is the generated code for 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(); } 

I was suspicious, so I changed your print message to include hashCode :

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

Then I saw the following:

ngAfterContentInit: demo 516575718

ngAfterContentInit: demo 57032191

This means that two different examples of the demonstration were alive, and not only one of them radiated twice. Then I added StackTrace.current to the demo constructor to get the stack trace:

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

And received:

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)

In turn, your component was created using the BuggyApp template:

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

nearer. Skillfully dig.

I commented on ContentItemDirective and saw that Demo was created once. I assume that you expected it to not be created at all due to *contentItem , so you kept digging - and finally figured it out.

Your StandardLayout component creates patterns:

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

But also your ContentItemDirective via ComponentResolver. I think you didn’t. Therefore, I will find out what you are trying to do and fix the problem by deleting the component creation in one of these places.

+3
source

Perhaps related to In Angular2, why is there 2 times to check the contents and view after setTimeout?

Are you in dev mode? The console tells you when the application loads. In dev Angular mode, run changeDetection twice so that it can detect side effects.

+2
source

Source: https://habr.com/ru/post/1269344/


All Articles