1

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.

Dargor
  • 93
  • 1
  • 9

3 Answers3

1

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]
})
...
Dargor
  • 93
  • 1
  • 9
  • Good to see the answer posted - didn't think you'd get the `ViewContainerRef` with an attribute directive – Drenai May 18 '22 at 14:20
0

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();
}
Zrelli Majdi
  • 1,204
  • 2
  • 11
  • 16
0

try this

ngAfterContentInit(){
  this.loadDynamicComponent();
}
apnnux
  • 361
  • 4
  • 11