32

I am trying to display a dynamic component similar (not exact) to the example in angular docs.

I have a dynamic directive with viewContainerRef

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

Excerpt from component code

@ViewChild(DynamicComponentDirective) adHost: DynamicComponentDirective;
..
ngAfterViewInit() {
let componentFactory = null;
                console.log(component);
                componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
                // this.adHost.viewContainerRef.clear();
                const viewContainerRef = this.adHost.viewContainerRef;
                viewContainerRef.createComponent(componentFactory);
}

Finally added <ng-template dynamicComponent></ng-template> in template

mperle
  • 3,342
  • 8
  • 20
  • 34

11 Answers11

41

I had the same problem. You have to add the directive into the AppModule:

 @NgModule({
  declarations: [
    AppComponent,
    ...,
    YourDirective,
  ],
  imports: [
   ...
  ],
  providers: [...],
  bootstrap: [AppComponent],
  entryComponents: [components to inject if required]
})
Michael
  • 411
  • 4
  • 4
35

You can take this approach: don't create directive, instead give an Id to ng-template

<ng-template #dynamicComponent></ng-template>

use @ViewChild decorator inside your component class

@ViewChild('dynamicComponent', { read: ViewContainerRef }) myRef

ngAfterViewInit() {
    const factory = this.componentFactoryResolver.resolveComponentFactory(component);
    const ref = this.myRef.createComponent(factory);
    ref.changeDetectorRef.detectChanges();
}
Rohit Sharma
  • 465
  • 4
  • 6
  • 2
    This worked, so thanks, but I'm curious as to WHY it works. My directive selector was correct, but for whatever reason could not be found. When I switched to ID selector it picked it right up. – meteorainer Aug 17 '18 at 22:21
  • 1
    It's not clear what `component` is supposed to refer to in `resolveComponentFactory(component)` – R. Salisbury Oct 16 '19 at 15:22
  • This worked for me..but how do you pass data to the component being dynamically created? – Kingsley Nov 30 '19 at 15:14
  • @Kingsley You can set properties on the component instance like this: `ref.instance.someInputData = data` – eskimwier Jul 15 '20 at 21:08
  • const viewContainerRef = this.adHost.viewContainerRef; This somehow doesn't work with a directive. When using a reference variable we should just call const viewContainerRef = this.adHost; viewContainerRef.createComponent(componentFactory); works just fine. – dgraf Jul 31 '20 at 16:02
18

In Angular 8 my fix was:

@ViewChild('dynamicComponent', {static: true, read: ViewContainerRef}) container: ViewContainerRef;
sampereless
  • 291
  • 4
  • 10
  • that fixed my issue – Raghava Kotekar Sep 26 '19 at 07:19
  • They mention it in their update guide (https://update.angular.io/#7.0:8.0l3) and Static query migration guide (https://angular.io/guide/static-query-migration), but your example makes it even clearer. – Jyrkka Feb 16 '20 at 02:09
  • Angular 11, same issue with the ng-if, solved by adding similar line ```@ViewChild('dynamicComponent', {static: true}) container: ViewContainerRef;``` – Denis Dec 12 '20 at 19:18
9

I ran into this problem as well and the reason was that the location which I wanted to load my component dynamicly into was inside an ng-if that was hidden initially.

<div *ngIf="items">
   <ng-template appInputHost></ng-template>
</div>

@ViewChild(InputHostDirective, { static: true }) inputHost: InputHostDirective;

Moving the ng-template to outside the ng-if solved the problem.

Ashkan Hovold
  • 898
  • 2
  • 13
  • 28
3

In my case, it was Angular 9 and the directive's selector was inside *ngIf. The solution I used was:

@ViewChild(ExampleDirective, {static: false}) exampleDirectiveHost: ExampleDirective;

Mark static is false due to reasons defined here Angular documentation

The other thing I did was use

ngAfterViewInit(){
   const viewContainerRef = this.exampleDirectiveHost.viewContainerRef;;
}
aCiD
  • 1,273
  • 1
  • 18
  • 28
2

Noted that I face the same problem if directive selector (dynamicComponent in this case) is at :

  1. first element of the component

  2. parent element with *ngIf condition

Hence, I avoid it by put it inside at non-root tag in the component html & load component to viewContainerRef only when the condition match.

  • I was suffering from undefined error of `viewContainerRef` element, spending some hours to find what was the problem. Finally, saw your answer and just instantiated empty object instead of checking it with *ngIf and viola! Thank you for your answer! – Khasan 24-7 Sep 21 '20 at 19:09
1

Template HTML file:

<ng-template #containerToHostDynamicallyInjectedComponents"></ng-template>

TS file

private componentRef: ComponentRef<COMPONENT_NAME_TO_CREATE> = null;

@ViewChild('containerToHostDynamicallyInjectedComponents', {read: ViewContainerRef}) hostContainer: ViewContainerRef;

constructor(private componentFactoryResolver:ComponentFactoryResolver) {}


ngOnInit() {

 const componentFactory = 
    this.componentFactoryResolver.resolveComponentFactory(<COMPONENT_NAME_TO_CREATE>);
    this.hostContainer.clear(); // CLEAR ANY INJECTED COMPONENT IF EXISTED.
    this.componentRef = this.hostContainer.createComponent(componentFactory);

}

```
  • this.componentRef: => stores reference of dynamically created component to access component public properties and methods and even subscribe to event emitter

ACESSS COMPONENT PROPERTIES AND METHODS USING:

 this.componentRef.instance.<PROPERTY_NAME>
 this.componentRef.instance.<METHOD_NAME>();

YOU CAN EVEN SUBSCRIBE TO @Output EVENT EMITTERS

this.componentRef.instance.<OUTPUT_PROPERTY_NAME>
.pipe(
  take(1)
)
.subscribe(response => console.log(response));

khizer
  • 1,242
  • 15
  • 13
0

The problem might also be that the selector of the viewRef (dynamicComponent in this example) does not match the one specified in the template of the component which uses the componentFactoryResolver.

Bwvolleyball
  • 2,593
  • 2
  • 19
  • 31
0

I just added the viewContainerRef stuff in the afterViewInit() cicle instead of the onInit() cicle, it worked for me :T

I am using Angular v11

 ngAfterViewInit(): void {
        const viewContainerRef = this.exampleHost.viewContainerRef;
        viewContainerRef.clear();
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(ExampleEntryComponent);
        viewContainerRef.createComponent(componentFactory);
    }
0

In my case, there was a typo in the directive selector on the ng-template, and thus it did not match with the one declared in the directive, thus ViewContainerRef was undefined. Simple cause, it was easy to overlook at first glance, as the error message may suggest having a more complex problem.

Janos
  • 650
  • 8
  • 13
-1

You see this error when the directive does not construct. You can set a breakpoint on the directive's constructor and check if the breakpoint ever hits. If not, that means that you are not loading the directive correctly. Then you can check that your component that is loading the directive it is properly adding the directive into the template.

EliJah
  • 89
  • 6