0

I can dynamically create a number of components and I want to be able to delete each one separately by clicking a button on each component. I have a working demonstration project which I kept as simple as possible. Please look at my stackblitz. You can create a number of components by clicking on the create button multiple times. That works the way I want it to work. But, I was hoping that there was an easier way. Here is app.component.ts

import {
  Component,
  ViewChild,
  ComponentFactoryResolver,
  ViewContainerRef,
  ComponentRef,
  OnDestroy
} from "@angular/core";
import { DynamicComponent } from "./dynamic/dynamic.component";
@Component({
  selector: "my-app",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnDestroy {
  private _count = 0;
  componentRef: ComponentRef<DynamicComponent>;
  dynamics: Map<string, ComponentRef<DynamicComponent>> = new Map();
  @ViewChild("viewcontainer", { read: ViewContainerRef, static: false })
  viewContainerRef: ViewContainerRef;
  constructor(private resolver: ComponentFactoryResolver) {}
  createComponent() {
    this._count++;
    const factory = this.resolver.resolveComponentFactory(DynamicComponent);
    this.componentRef = this.viewContainerRef.createComponent(factory);
    this.componentRef.instance.name = " " + this._count;
    this.dynamics.set(this.componentRef.instance.name, this.componentRef);
    this.componentRef.instance.deleteName.subscribe(
      name => this.destroyComponent(name),
      error => console.log(error)
    );
  }
  destroyComponent(name: string) {
    if (this.dynamics.has(name)) {
      this.dynamics.get(name).destroy();
    }
    this.dynamics.delete(name);
  }
  ngOnDestroy() {
    this.dynamics.forEach(
      (value: ComponentRef<DynamicComponent>, key: string) => {
        value.instance.deleteName.unsubscribe();
      }
    );
    this.dynamics = null;
  }
}

here is app.component.html

<button (click)="createComponent()">Create a Component</button>
<ng-container #viewcontainer></ng-container>

here is dynamic.component.ts

import { Component, Input, Output, EventEmitter } from "@angular/core";

@Component({
  selector: "app-dynamic",
  templateUrl: "./dynamic.component.html",
  styleUrls: ["./dynamic.component.css"]
})
export class DynamicComponent {
  @Input() name: string;
  @Output() deleteName = new EventEmitter<string>();
  constructor() {}
  delete() {
    this.deleteName.emit(this.name);
  }
}

here is dynamic.component.html

<div>
    {{ name }} <button (click)="delete()">Delete</button>
</div>

I added the following line to app.module.ts

entryComponents: [DynamicComponent]

My question: Is there an easier way? Could @ViewChildren be used? Something like?

@ViewChildren(DynamicComponent) dynamics: QueryList<DynamicComponent>;  

EDIT: After a good night's sleep, I have another idea. Why not have each dynamic component hold a reference to its self. Please look at my second stackblitz . That project doesn't use a map to hold all the ComponentRefs of all the dynamics. I just save a ComponentRef in each DynamicComponent. But, it throws errors.

DynamicComponent_Host.ngfactory.js? [sm]:1 ERROR RangeError: Maximum call stack size exceeded
    at callWithDebugContext (services.ts:636)
    at Object.debugDestroyView [as destroyView] (services.ts:355)
    at ViewRef_.destroy (refs.ts:284)
    at ComponentRef_.destroy (refs.ts:122)
    at DynamicComponent.ngOnDestroy (dynamic.component.ts:20)
    at callProviderLifecycles (provider.ts:575)
    at callElementProvidersLifecycles (provider.ts:541)
    at callLifecycleHooksChildrenFirst (provider.ts:529)
    at destroyView (view.ts:511)
    at callWithDebugContext (services.ts:630)

I don't understand why my second project doesn't work. The first one(at the top here) works. EDIT: I found How to let a component delete itself on a button click with in angular
I guess that would be a lot easier than working with ComponentFactoryResolver. EDIT: I found a similar question and solution at How to destroy component created dynamically angular 8
EDIT: I just found What is the proper use of an EventEmitter? So, guess my first project misuses EventEmitter?

rickz
  • 4,324
  • 2
  • 19
  • 30

1 Answers1

0

In your second project, you are calling this.selfref.destroy() inside the ngOnDestroy life-cycle callback. This creates an infinite loop because ngOnDestroy is triggered by the call to this.selfref.destroy(). You shouldn't need to explicitly handle the ComponentRef like that. It is a reference, after all.

If you remove the call to this.selfref.destroy(), your second project works like a charm.

night_owl
  • 856
  • 6
  • 12
  • I you are right. When I remove that line in DynamicComponent's ngOnDestroy method the error goes away. I am still wondering about ComponentFactory. I think it all gets too expensive. Perhaps, the way they generated components at https://stackoverflow.com/questions/54112649/how-to-let-a-component-delete-itself-on-a-button-click-with-in-angular?rq=1 is a good alternative? – rickz Mar 24 '20 at 20:25