Component constructor is getting called twice with Angular elements
Asked Answered
M

3

0

I am working on a recursive query builder form, which is something similar to this, in angular 7, with reactive form. Means, the user can keep adding a parallel rule by clicking on Add rule and can add a group by clicking on Add group. I have created two components, QueryDesignerComponent and QueryComponent. QueryDesignerComponent holds the outer container, with AND and OR condition and QueryComponent holds the row input, that's LHS, operator and RHS.

  1. When user clicks Add rule I am growing the rule just by pushing one more entry with QueryComponent inside QueryDesignerComponent. I am repeating this with *ngFor.
  2. When the user clicks Add group I am callings the QueryDesignerComponent inside QueryComponent, which makes it recursive. I am repeating this with *ngFor.

I am done with the implementation and its working fine within angular app, which has an angular environment.

Now, I am trying to port this flow in angular elements, to make it reusable, regardless of the environment.

this is how I am placing first row [ QueryComponent ] inside QueryDesignerComponent,

<div class="qd--criteria">
    <div class="row qd--body qd--clear-margin-lr">
        <div class="col-md-12 qd--condition-container">
            <query [data]="data" [operators]="operators" [(queryForm)]="queryForm"></query>
        </div>
    </div>
</div>

and this way I am managing the parallel query and groups inside QueryComponent,

<!--Top level container for query view | everything related should go here: start-->
<div class="qd--query-container" [formGroup]="queryForm" *ngIf="queryForm">
    <div class="row" formArrayName="queries">
        <!--Repeat the dynamically added/removed queries: start-->
        <div class="col-md-12 qd--query-inputs-container" *ngFor="let query of currentQueries.controls; let queryIndex = index">
            <div class="row qd--query-inputs" [formGroupName]="queryIndex">
                <!--Actual query inputs: start-->
                <div class="col-md-10 qd--condition-holder">
                    <div class="row no-gutter">
                        <!--Left hand side input: start-->

                        <!--Left hand side input: end-->

                        <!--Operator: start-->

                        <!--Operator: end-->

                        <!--Right hand side input: start-->

                        <!--Right hand side input: end-->
                    </div>
                </div>
                <!--Actual query inputs: start-->

                <!--Group options: start-->

                <!--Group options: end-->

                <!--Group query: start-->
                <div *ngIf="query !== undefined" class="ai--query-groups">
                    <div *ngFor="let group of getGroups(query).controls; let groupIndex=index" class="ai--query-group">
                        <query-designer
                                [data]="data"
                                [operators]="operators"
                                [queryForm]="group"
                                (removeQueryGroup)="removeQueryGroupHandler($event)"
                                [queryIndex]="queryIndex"
                                [groupIndex]="groupIndex"></query-designer>
                    </div>
                </div>
                <!--Group query: end-->

            </div>
        </div>
        <!--Repeat the dynamically added/removed queries: end-->
    </div>
</div>
<!--Top level container for query view: start-->


<!--Repeat the dynamically added/removed queries: start-->
<div class="col-md-12 qd--query-inputs-container" *ngFor="let query of currentQueries.controls; let queryIndex = index">
    <div class="row qd--query-inputs" [formGroupName]="queryIndex">
        <!--Actual query inputs: start-->
        <div class="col-md-10 qd--condition-holder">
            <div class="row no-gutter">
                <!--Left hand side input: start-->

                <!--Left hand side input: end-->

                <!--Operator: start-->

                <!--Operator: end-->

                <!--Right hand side input: start-->

                <!--Right hand side input: end-->
            </div>
        </div>
        <!--Actual query inputs: start-->

        <!--Group options: start-->

        <!--Group options: end-->

        <!--Group query: start-->
        <div *ngIf="query !== undefined" class="ai--query-groups">
            <div *ngFor="let group of getGroups(query).controls; let groupIndex=index" class="ai--query-group">
                <query-designer
                        [data]="data"
                        [operators]="operators"
                        [queryForm]="group"
                        (removeQueryGroup)="removeQueryGroupHandler($event)"
                        [queryIndex]="queryIndex"
                        [groupIndex]="groupIndex"></query-designer>
            </div>
        </div>
        <!--Group query: end-->

    </div>
</div>
<!--Repeat the dynamically added/removed queries: end-->

This is how I am creating a custom angular element,

@NgModule({
  imports: [
    CommonModule,
    BrowserModule,
    NgSelectModule,
    FormsModule,
    ReactiveFormsModule,
    CoreModule
  ],
  declarations: [
    AppComponent,
    QueryComponent,
    QueryDesignerComponent
  ],
  entryComponents: [QueryDesignerComponent],
  providers: []
})
export class AppModule {
  constructor(private injector: Injector) {
    const strategyFactory = new ElementZoneStrategyFactory(QueryDesignerComponent, injector);
    const customElement = createCustomElement(QueryDesignerComponent, { injector, strategyFactory });
    customElements.define('query-designer', customElement);
  }

  ngDoBootstrap() { }
}

In the first render, it's working fine and I am able to add n number of parallel rows. But, when I click the Add group, QueryDesignerComponent's constructor is getting called twice! This makes the first instance of QueryDesignerComponent to receive undefined and second instance to receive correct values.

I followed why ngOnInit called twice?, related github issue and ngOnInit and Constructor are called twice but, I found no luck!!

Does anyone know how can I get rid of this issue? Any help/guidance would be much appreciated!

Melina answered 11/2, 2019 at 14:25 Comment(6)
Please provide more code to make this issue reproducable.Sherer
Did you try to load this module with lazy loading ? angular.io/guide/lazy-loading-ngmodules github.com/angular/angular/issues/12869Schoolgirl
@web.dev, added more code. please have a lookMelina
@JoelGarciaNuño, let me check it. this is a very quick question before jumping the actual link, but does it help in angular elements?Melina
This help to load a single time the component, may don't call again the constructor. For use lazy load, use # on the path of your router angular.io/guide/lazy-loading-ngmodules#routes-at-the-app-level but take a break, may a simple issue, when you press the button more than two times, how many times are called the constructor ?Schoolgirl
@JoelGarciaNuño, 1. I am not using the router, it just needs to render the view and emit the output to outside HTML view 2. each time I click the Add group button, constructor gets invoked twice and as it's not receiving the correct values, group view ain't getting renderedMelina
C
5

I just had the same issue, with Angular Elements too.

Problem was that I was calling an Angular7 component inside an Angular7 component, and on app.module.ts I had bootstrapped the child component with the same name as the Angular7 component name.

When parent called my-child-component it was calling both the Angular7 one and the Custom Element one.

So, the fix is to use different names in this case.

Chromogen answered 1/3, 2019 at 21:23 Comment(0)
W
2

Another possibility might be the following piece of code in app.module.ts

platformBrowserDynamic().bootstrapModule(AppModule).catch(err => { console.error(err) });
Wooton answered 15/4, 2019 at 14:8 Comment(3)
Can you please explain further how this causes the issue.Brimful
This actually worked for me! I was seeing my components getting loaded twice when first loading the page. Removing that line fixed the issue, I'm still not sure why, though.Baffle
This is the answer that leads to the right understanding. Most of the times, when AppComponent constructor is being called twice, means that the angular app is being bootstrapped twice. How it happens? Either you wrongly configured the bootstrap prop of @ngModule decorator in AppModule, or you messed with file main.ts & you're calling bootsrap method twiceModel
F
1

I tried this and worked for Angular elements. Removed Bootstrap from @NgModule and added the custom element definition in ngDoBootstrap instead of constructor.

Please find below code for reference.

 @NgModule({
  declarations: [
    MyComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  schemas: [
    CUSTOM_ELEMENTS_SCHEMA
  ],
  providers: [MyService]
})
export class AppModule implements DoBootstrap{ 
  constructor(private injector: Injector) {
  }
  ngDoBootstrap(appRef: ApplicationRef) {
    const el = createCustomElement(MyComponent, { injector: this.injector });
    customElements.define('my-widget', el);
  }
}
Funnelform answered 16/9, 2020 at 6:50 Comment(1)
thx solved the issue for meOverarm

© 2022 - 2024 — McMap. All rights reserved.