1

I have a directive that reads src attribute and appends a DynamicComponent. It also sets an input property of DynamicComponent.

@Directive({
  selector: '[appDynamic]'
})
export class InjectDirective {
  @ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;

  constructor(
    private element: ElementRef,
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver
  ) { }

  ngOnInit(){
    const nativeElement = this.element.nativeElement;
    const src = nativeElement.getAttribute('src');

    if (src) {
      const componentFactory = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
      const componentRef = this.viewContainerRef.createComponent(componentFactory);
      componentRef.instance.mediaPath = src;
      const parent = this.element.nativeElement.parentElement;
      parent.insertBefore(componentRef.location.nativeElement, this.element.nativeElement.nextSibling)
    }
  }
}

The injection happens, but I can't read mediaPath in the DynamicComponent due to this not being set to the right value. this is an object that only has property __ngContext__.

export class DynamicComponent implements OnInit {

  @Input() mediaPath:string;
  constructor() { }

  ngOnInit(): void {
  }

  openSection(event){ // click event
    console.log(this); // this does not refer to instance of DynamicComponent
    console.log(this.mediaPath); // this.mediaPath is undefined
  }

}

What is the correct way of creating a component dynamically and also passing values to the component properties? I am using angular 11.

UPDATED to Angular 14. Still the same problem:

@Directive({
  selector: '[appInject]'
})
export class InjectDirective {
  @ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;

  constructor(
    private element: ElementRef,
    private viewContainerRef: ViewContainerRef,
  ) { }

  ngOnInit(){
    const nativeElement = this.element.nativeElement;
    const src = nativeElement.getAttribute('src');

    if (src) {
      const componentRef = this.viewContainerRef.createComponent(DynamicComponent);
      componentRef.setInput("mediaPath",src)
      const parent = this.element.nativeElement.parentElement;
      parent.insertBefore(componentRef.location.nativeElement, this.element.nativeElement.nextSibling)
    }
  }
}


export class DynamicComponent implements OnInit {

  @Input() mediaPath:string;
  constructor() { }

  ngOnInit(): void {}
  
  openFindSimilarSection(event){
    console.log(this); // only has __ngContext__ property
    console.log(this.mediaPath); // this is undefined
  }
}

//used like this:

  <img [src]="dynamically.added.path" appInjectFindSimilar>
6
  • what angular version are you using? component factory resolver is deprecated for ages now Commented Jun 14 at 10:04
  • @Andrei angular 11. is it time for me to upgrade? Commented Jun 14 at 10:28
  • depends on you. generally with angular evergreen strategy (just following the latest version you can considering your dependencies) is a pretty good strategy Commented Jun 14 at 10:36
  • @Andrei upgrade was almost seamless. Now at version 14 with setInput() available. Still the same issue, even with using setInput. Also added example on how it is used in html. Commented Jun 14 at 13:37
  • how do you call openFindSimilarSection? Commented Jun 14 at 15:56

1 Answer 1

1

Try using setInput method to pass in the input.

  const componentRef =
    this.viewContainerRef.createComponent(componentFactory);
  componentRef.setInput('mediaPath', src);

Full Code:

import {
  Component,
  Input,
  Directive,
  ViewChild,
  ViewContainerRef,
  ComponentFactoryResolver,
  ElementRef,
  Renderer2,
  Attribute,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';

@Component({
  selector: 'dynamic-component',
  template: `dynamic component`,
})
export class DynamicComponent {
  @Input() mediaPath!: string;
  constructor() {}

  ngOnInit(): void {
    // click event
    console.log(this); // this does not refer to instance of DynamicComponent
    console.log(this.mediaPath); // this.mediaPath is undefined
  }

  openSection(event: any) {}
}

@Directive({
  selector: '[appDynamic]',
})
export class InjectDirective {
  @ViewChild('container', { read: ViewContainerRef })
  container!: ViewContainerRef;

  constructor(
    private element: ElementRef,
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver,
    private renderer: Renderer2,
    @Attribute('src') public src: string
  ) {}

  ngOnInit() {
    const nativeElement = this.element.nativeElement;

    if (this.src) {
      const componentFactory =
        this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
      const componentRef =
        this.viewContainerRef.createComponent(componentFactory);
      componentRef.setInput('mediaPath', this.src);
      // componentRef.instance.mediaPath = src;
      const parent = this.element.nativeElement.parentElement;
      parent.insertBefore(
        componentRef.location.nativeElement,
        this.element.nativeElement.nextSibling
      );
    }
  }
}

@Component({
  selector: 'app-root',
  imports: [InjectDirective],
  template: `
    <img appDynamic [src]="'test'"/>
  `,
})
export class App {
  name = 'Angular';
}

bootstrapApplication(App);

Stackblitz Demo

Sign up to request clarification or add additional context in comments.

4 Comments

thanks. setInput is not available in angular 11
Upgraded to version 14 where setInput is available. Same issue as before :(
@sanjihan could you replicate on the stackblitz and share back
did what you said. could not reproduce the issue. it was an error on my end

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.