5

I'm using Angular 8 for a project, and I'm having trouble launching a dynamic component because ngOnInit doesn't get called. I've build a directive called PlaceholderDirective, shown below:

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

And used that directive on a ng-template:

<ng-template appPlaceholder></ng-template>

The component that holds the HTML where the ng-template lies launches the dynamic component, as such:

@ViewChild(PlaceholderDirective, {static: false}) private placeholder: PlaceholderDirective;
...
constructor(private componentResolver: ComponentFactoryResolver) {}
...
public launchSensorEditComponent(id: number) {
    const componentFactory = this.componentResolver.resolveComponentFactory(SensorEditComponent);
    const hostViewContainerRef = this.placeholder.viewContainerRef;
    hostViewContainerRef.clear();
    const sensorEditComponentRef = hostViewContainerRef.createComponent(componentFactory);
    sensorEditComponentRef.instance.sensorId = id;
}

The actual dynamic component is very simple, and its registered in the entryComponents section in AppModule. I've narrow it down to the bare minimum in order to understand what's wrong:

@Component({
  selector: 'app-sensor-edit',
  templateUrl: './sensor-edit.component.html',
  styleUrls: ['./sensor-edit.component.css']
})
export class SensorEditComponent implements OnInit {
  @Input() sensorId: number;
  private showDialog: boolean = false;

  constructor() {
    console.log('constructor');
  }

  ngOnInit() {
    console.log('ngOnInit');
    this.showDialog = true;
}

Last, the HTML for the dynamic component:

<div *ngIf="showDialog">
  <h3>its not working</h3>
</div>

The problem is that ngOnInit from the dynamic component doesn't get called and I have the rest of the data to initialize in there, so that I can build the rest of the template.

The really weird part is that if my console is not opened, and I open it or vice-versa, the template is shown.

codehero
  • 499
  • 3
  • 15
  • https://dzone.com/articles/how-to-use-change-detection-in-angular and you need to mark the view dirty. https://angular.io/api/core/ChangeDetectorRef#markforcheck – Reactgular Aug 30 '19 at 18:45
  • 2
    I managed to make it work by calling detectChanges() on the viewContainer – codehero Aug 30 '19 at 18:53
  • 1
    That solves the problem with a sledge hammer. – Reactgular Aug 30 '19 at 18:56
  • I understand, I'll try with the links you gave me later, but right now I'll leave with the sledge hammer method. Thanks for your help! – codehero Aug 30 '19 at 19:17

1 Answers1

2

Based on the answers in comment by @codingandstuff above, just want to make it more clear with code changes for the example above.

Also, I would like this answer to be here as a reference for anybody who googles "how to generate / inject / render dynamic component in angular".

  1. We need to add public property changeDetectorRef to the directive:
@Directive({
  selector: '[appPlaceholder]'
})
export class PlaceholderDirective {
  constructor(
    public viewContainerRef: ViewContainerRef,
    public changeDetectorRef: ChangeDetectorRef,
  ) {}
}
  1. Now we need to use it after setting instance properties:
@ViewChild(PlaceholderDirective, {static: false}) private placeholder: PlaceholderDirective;
...
constructor(private componentResolver: ComponentFactoryResolver) {}
...
public launchSensorEditComponent(id: number) {
    const componentFactory = this.componentResolver.resolveComponentFactory(SensorEditComponent);
    const hostViewContainerRef = this.placeholder.viewContainerRef;
    hostViewContainerRef.clear();
    const sensorEditComponentRef = hostViewContainerRef.createComponent(componentFactory);
    sensorEditComponentRef.instance.sensorId = id;
    this.placeholder.changeDetectorRef.detectChanges(); // <-- this line has been added
}
  1. Enjoy:)

And if you've upgraded Angular to 13+, then please note that now you can provide component class directly to createComponent without dealing with componentResolver, componentFactory etc. Just like this:

public launchSensorEditComponent(id: number) {
    const { hostViewContainerRef, changeDetectorRef } = this.placeholder;
    hostViewContainerRef.clear();
    const sensorEditComponentRef = hostViewContainerRef.createComponent(SensorEditComponent);
    sensorEditComponentRef.instance.sensorId = id;
    changeDetectorRef.detectChanges();
}
Maxím G.
  • 860
  • 10
  • 14