6

Summary of what I'm trying to accomplish

  • dynamically add components to a ViewContainerRef (done)
  • initialized these Dynamic components with properties (done)
  • get access to the dynamically created Component instances so that I may make decisions based what is.

the problem

  • when adding components dynamically, they are added to a ViewContainerRef
  • ViewContainerRef offers methods, such as get(index) that return ViewRef.
  • ViewRef doesn't appear to have any relationship to the Component instance, making it a challenge to obtain the required data

Here is a Stackblitz Link with working code show below (for dynamically creating the components)

the appComponent starts off by creating a few component using ComponentFactoryResolver, and adding them to the ViewChild defined in the template. each DynamicComponent is initialized with an id property value that we are trying to reference after creation

@Component({
  selector: "my-app",
  template: `
    <h3>Retrieving Component Reference for Dyamic Compnents</h3>
    <button (click)="getCompRef()">Get References</button>
    <div>
      <ng-container #childCont></ng-container>
    </div>
    <div>
      <small>List out the Ids from the dynamic components</small> <br />
      {{ createdItemIds | json }}
    </div>
  `,
  styleUrls: ["./app.component.css"]
})
export class AppComponent implements AfterViewInit {
  @ViewChild("childCont", { read: ViewContainerRef })
  childCont: ViewContainerRef;

  createdItemIds: string[] = [];

  itemLimit = 5;

  constructor(
    private fr: ComponentFactoryResolver,
    private cdr: ChangeDetectorRef
  ) {}

  ngAfterViewInit(): void {
    for (let i = 0; i < this.itemLimit; i++) {
      const factory = this.fr.resolveComponentFactory(DynamicComponent);
      const compRef = this.childCont.createComponent(factory, i);
      // Set the id of the instance so that we can use it later
      compRef.instance.id = i + 1;
      this.cdr.detectChanges();
    }
  }
  ...
}

the DynamicComponent being added is fairly simple. for simplifications purposes, it only contains a single id property that we are trying to get at

@Component({
  selector: "dynamic-component",
  template: `
    <div>Dynamic Component: {{ id }}</div>
  `,
  styles: [``]
  ]
})
export class DynamicComponent {
  id: number;
} 

Everything is fine so far.

  • The components are dynamically created
  • the component instances are initialized with the ID, which we can see by the fat that it is displayed in the UI

the issue comes with trying to retrieve the ID property from the DynamicallyCreated components.

In the AppComponent, when the user clicks the button, the getCompRef() method gets called and loops through each child of the childCont (ViewContainerRef)

getCompRef(): void {
  for (let i = 0; i < this.itemLimit; i++) {
    const viewRef = this.childCont.get(i);
    // How do I get at the instance of the view  in order to obtain id?
    // the view Ref doesn't provide access to the instance
    // console.log(viewRef);
  }
}

however the ViewRef returned from ViewContainerRef.get() is an sub class of ChangeDetectoreRef and doesn't hold any reference to the instance in question.

In doing research on this issue, it tried going down the path of using ViewChildren to get the list components being created, but this didn't work because of issues such as

  • https://github.com/angular/angular/issues/8785
  • or examples assume the directive used in the ViewChildren selector is for a component that has been predefined in a template
  • I see a lot of question in reference to some folks looking to get the ViewRef when they have the Component.instance, but that isn't helpful in this situation.

Ultimately my question is,

  • is there a simple way to get at the Component instance from a ViewRef that I'm missing

Any help is appreciated.

thank you.

Edward
  • 1,076
  • 1
  • 12
  • 24
  • View children should be able to get all children of the given type, have you tried that? – JWP Oct 21 '20 at 02:09
  • yes, I tried using `@ViewChildren(DynamicComponent) children: QueryList;` but the queryList is always empty and never includes the components that were added. – Edward Oct 21 '20 at 14:50
  • 1
    This means that when the list is gathered the components are not there yet or ever. You have to look at the children after afterViewInit or later to see rendered content. If they aren't showing then 1) The wrong type was specified 2) They are not showing or 3) Something else. – JWP Oct 21 '20 at 19:46
  • 4
    why don't you store the instance in a variable or all the dynamic instances in a map when you are adding them to the dom? `compRef.instance.id = i + 1;` you are already accessing the instance at this line. – HirenParekh Oct 23 '20 at 10:28
  • @HirenParekh that is my last resort. I was trying to avoid doing that to reduce how much I'm storing in memory. and I assumed Angular is already keeping track of this somewhere, and doing so more efficiently. but if nothing else is possible, yes, that will be my only recourse – Edward Oct 23 '20 at 12:45
  • 1
    There is no way to get instance from ViewRef except some hacky non-stable solutions. Store instances somewhere when you're creating dynamic components and use that store later – yurzui Oct 24 '20 at 04:50
  • @Edward link you shared is broken – Iam Coder Oct 28 '20 at 16:45
  • @IamCoder link has been fixed. at this point, I've moved on from trying to user View Children based on some comments above. I'm resorting to storing references and handling manual cleanup of those references manually. – Edward Oct 28 '20 at 20:46
  • @yurzui is there a way to get it from ngFor created children components? – Den Kerny Jul 29 '21 at 14:22
  • @DenKerny Can you please demonstrate that case in stackblitz? – yurzui Jul 29 '21 at 14:27
  • @yurzui im trying to find a way to not destroy selected child component in ngFor after update its array, i have to replace it in another DOM place to 'store' subscriptions on showing drag and drop preview's. Let's say im starting onDragStarted event and then i want to 'cut' selected child component (cdkDropList in) with its ref to prevent ngOnDestroy on cdkDropList after array is updated, cause it hides preview element while moving dragitem – Den Kerny Jul 30 '21 at 09:46
  • @yurzui So i want to dragging cdkDrag placed in ngFor-ed cdkDropList (using cdkDropListGroup) even array is updated..Now im trying it with dynamic creating against ngFor-ed (where i cant affect on ngFor-ed child's ngOnDestroy ) – Den Kerny Jul 30 '21 at 09:58

1 Answers1

0

I could only do this by keeping track of the instances of these components in a different array.

@Component({
  selector: "my-app",
  template: `
    <h3>Retrieving Component Reference for Dyamic Compnents</h3>
    <button (click)="getCompRef()">Get References</button>
    <div>
      <ng-container #childCont></ng-container>
    </div>
    <div>
      <small>List out the Ids from the dynamic components</small> <br />
      {{ createdItemIds | json }}
    </div>
  `,
  styleUrls: ["./app.component.css"]
})
export class AppComponent implements AfterViewInit {
  @ViewChild("childCont", { read: ViewContainerRef })
  childCont: ViewContainerRef;

  createdItemIds: string[] = [];

  itemLimit = 5;
  private dynamicComponentsArray: ComponentRef<DynamicComponent>[] = [];

  constructor(
    private fr: ComponentFactoryResolver,
    private cdr: ChangeDetectorRef
  ) {}

  ngAfterViewInit(): void {
    for (let i = 0; i < this.itemLimit; i++) {
      const factory = this.fr.resolveComponentFactory(DynamicComponent);
      const compRef = this.childCont.createComponent(factory, i);
      // Set the id of the instance so that we can use it later
      compRef.instance.id = i + 1;
      this.dynamicComponentsArray.push(compRef);
      this.cdr.detectChanges();
    }
  }
  
  getCompRef(): void {
      for (let i = 0; i < this.itemLimit; i++) {
        const viewRef = this.childCont.get(i);
        if (this.dynamicComponentsArray.length > 0) {
            for (let i = 0; i < this.dynamicComponentsArray.length; i++) {
                const componentRef = this.dynamicComponentsArray[i];
                const component = (componentRef) as ComponentRef<DynamicComponent>;
                let value = component.instance.id;
            }
        }
      }
    }
}