简体   繁体   中英

Ionic 6 - Viewchild null in dynamic component loader

I´m developing an Ionic application that will have a form with controls created dinamically from JSON data.

load-child.directive.ts:

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[dynamicChild]'
})
export class DynamicChildLoaderDirective {
  constructor(public viewContainerRef: ViewContainerRef) { }
}

app.module.ts:

...
@NgModule({
  declarations: [AppComponent, DynamicChildLoaderDirective],
...

home.page.html

...
<ion-content [fullscreen]="true" class="ion-padding">
  
    <form [formGroup]="testForm">
      <ion-card *ngFor="let objGrupo of grupos">
        <ion-card-header>
          <ion-card-title>{{objGrupo.label}}</ion-card-title>
        </ion-card-header>
        
        <ion-card-content *ngFor="let objControl of objGrupo.controls">
          <app-dynamic-form [control]="objControl" [form]="testForm"></app-dynamic-form>
        </ion-card-content>    
      </ion-card>
      
    </form>
</ion-content>
...

home.page.ts:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DynamicFormGroup } from '../models/dynamic_form_group';
import { Ticket } from '../models/ticket';
import { TicketService } from '../services/ticket.service';


@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {

  grupos: DynamicFormGroup[] = [];
  testControl: any = {};
  testForm: FormGroup;
  validation_messages: any;

  constructor(public formBuilder: FormBuilder,private ticketService: TicketService) {

    this.ticketService.obtenerFormularioTicket()
      .subscribe(obtainedInfo => {
        var ticket: Ticket = obtainedInfo;
        this.grupos = ticket.getFormList();
        this.testControl = this.grupos[0].controls;
      });
  }

}

dynamic-form.component.html:

<ion-label>{{ control.Name }}</ion-label>  

<ng-template dynamicChild></ng-template>

dynamic-form.component.ts:

import { Component, Input, OnInit, Type, ViewChild } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { DynamicChildLoaderDirective } from 'src/app/directives/load-child.directive';
import { DnElement } from '../dn-element/dn-element.component';
import { DnInputTextComponent } from '../dn-input-text/dn-input-text.component';
import { DnLabelComponent } from '../dn-label/dn-label.component';

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html',
  styleUrls: ['./dynamic-form.component.scss'],
})
export class DynamicFormComponent implements OnInit {
  @ViewChild(DynamicChildLoaderDirective, { static: true }) dynamicChild!: DynamicChildLoaderDirective;
  @Input() control: any;
  @Input() form: FormGroup;
  componente: Type<any>;  
  
  ngOnInit(): void {
    this.loadDynamicComponent();
  }

  private loadDynamicComponent() {
    if(!this.control.Editable){
      this.componente = DnLabelComponent;
    }else{
      this.componente = DnInputTextComponent;
    }  
    const componentRef = this.dynamicChild.viewContainerRef.createComponent<DnElement>(this.componente);
    componentRef.instance.objControl = this.control;
    componentRef.instance.form = this.form;
  }
}

The problem is when running the application, the line

const componentRef = this.dynamicChild.viewContainerRef.createComponent<DnElement>(this.componente);

returns the following error:

ERROR Error: Uncaught (in promise): TypeError: this.dynamicChild is undefined

I´m using the following versions:

Ionic Version: 6.1.5

Angular Version: 13.3.5

I´m Using the offical angular docs as guide:

https://angular.io/guide/dynamic-component-loader

I´ve tried to change the static property of the ViewChild to false and move the ngOnInit to the ngAfterViewInit with the same result.

Is there anything missing here?

Thanks in advance.

Edit:

I´ve also tried with viewChildren:

export class DynamicFormComponent implements OnInit {
  @ViewChild(DynamicChildLoaderDirective, { static: true }) dynamicChild!: DynamicChildLoaderDirective;
  @ViewChildren(DynamicChildLoaderDirective) viewChildren!: QueryList<DynamicChildLoaderDirective>;
  @Input() control: any;
  @Input() form: FormGroup;
  componente: Type<any>;  
  
  ngOnInit(): void {

    //this.loadDynamicComponent();
  }

  ngAfterViewInit() {
    // viewChildren is set
    this.viewChildren.changes.subscribe((r) => {
      console.log(this.viewChildren);
    });

  }

No error presents but it never enters the console.log part of the subscribe.

Finally I managed to get it work. The code is ok as is, with the difference that the declaration of the directive has to be done in the page in which I´m using it. In this case in home.module.ts instead of app.module.ts:

...
@NgModule({
  imports: [
    CommonModule,
    FormsModule,ReactiveFormsModule,
    IonicModule,
    HomePageRoutingModule
  ],
  declarations: [HomePage,DynamicFormComponent,DynamicChildLoaderDirective]
})
...

You should call template references when your page fully loaded and for that We have a lifecycle hooks triggered when your page fully loaded:

ngAfterViewInit(){
    this.loadDynamicComponent();
}

try this

ngAfterContentInit(){
  this.loadDynamicComponent();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM