5

I'm new to Angular. I have a button that every time I click it, it creates a component dynamically. I need each component to have a button or something that can destroy that component specifically. I have a function in the dynamic component that when I click on it, that component must be closed, but I don't know how to pass it to the function of the typescript file.Please help me.

app.component.ts

import { Component, OnInit, ComponentFactoryResolver, ViewChild, Input,ComponentRef,ViewContainerRef } from '@angular/core';
import {ChatService} from "./services/chat.service";
import {Mensaje} from "./models/mensaje";
import {ConversacionComponent} from "./components/conversacion/conversacion.component";
import {ConversacionDirective} from "./components/conversacion/conversacion.directive";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
    providers:[ChatService]
})
export class AppComponent {
    @ViewChild(ConversacionDirective, {static: true}) eldinamico: ConversacionDirective;
  title = 'chat';

  constructor(private cfr: ComponentFactoryResolver){  }
    ngOnInit() { }


    componenteDinamico(mensaje: string) {
        const cf = this.cfr.resolveComponentFactory(ConversacionComponent);
        const vcr = this.eldinamico.viewContainerRef;
        vcr.createComponent(cf, 0);
    }
}

conversacion.directive.ts

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
    selector: '[appConversacionDinamica]'
})
export class ConversacionDirective {

    constructor(public viewContainerRef: ViewContainerRef) { }

}

app.component.html

<input type="text" #mensaje><br/>
<button (click)="componenteDinamico(mensaje.value)"> Crear Componente </button>
<br/>
<div class="orden">
  <ng-template appConversacionDinamica></ng-template>

</div>

conversacion.component.html


<button (click)="removeObject()">delete me</button>
<div>
    this is a component dynamically
</div>

conversacion.component.ts

import {Component, Input, OnInit, Output, EventEmitter,ViewChild,ElementRef,ComponentRef} from '@angular/core';

@Component({
  selector: 'app-conversacion',
  templateUrl: './conversacion.component.html',
  styleUrls: ['./conversacion.component.css']
})
export class ConversacionComponent implements OnInit {
    mensaje: string;   
    vcr:any; 

  constructor() {}

  ngOnInit() {}   

  removeObject(){
    this.vcr.destroy();
    }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Jesús Soto Mitjans
  • 131
  • 1
  • 2
  • 11

1 Answers1

6

Below is an example where the dynamic component can "delete itself". The creator (app.component.ts) subscribes to the output of the dynamic component (simple.component.ts) and then invokes .destroy().

Also, the SimpleComponent has to be included in the module as an entryComponent since it is created dynamically.

Giphy: https://giphy.com/gifs/W2zx2dhNk4znnYFyGT

Example:


app.component.html

<h1>App</h1>
<button (click)="onClickAdd()">Create</button>

<br>
<hr>
<ng-template #componentsContainer></ng-template>

app.component.ts

import {
  Component,
  ViewChild,
  ViewContainerRef,
  ComponentFactoryResolver,
  ComponentRef,
  OnDestroy
} from '@angular/core';
import { Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';
import { SimpleComponent } from './simple/simple.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnDestroy {
  @ViewChild('componentsContainer', { read: ViewContainerRef }) container: ViewContainerRef;
  private subs: Subscription[] = [];

  ngOnDestroy() {
    // unsubscribe from all on destroy
    this.subs.forEach(sub => sub.unsubscribe());
  }

  onClickAdd = () => {
    const factory = this.componentFactoryResolver.resolveComponentFactory(SimpleComponent);
    const component = this.container.createComponent(factory);

    component.instance.numberCreated = this.container.length;

    // subscribe to component event to know when to delete
    const selfDeleteSub = component.instance.deleteSelf
      .pipe(tap(() => component.destroy()))
      .subscribe();

    // add subscription to array for clean up
    this.subs.push(selfDeleteSub);
  }

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
}

simple.component.html

<button (click)="deleteSelf.emit()" style="background-color: blue; color: white">delete self</button>
<p>Dynamic Component</p>
<p>Number at time of creation: {{ numberCreated }}</p>
<hr>

simple.component.ts

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

@Component({
  selector: 'app-simple',
  templateUrl: './simple.component.html',
  styleUrls: ['./simple.component.css']
})
export class SimpleComponent implements OnInit {
  @Output() deleteSelf: EventEmitter<void> = new EventEmitter<void>();
  @Input() numberCreated: number;

  constructor() { }

  ngOnInit() {
  }

}

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { SimpleComponent } from './simple/simple.component';

@NgModule({
  declarations: [AppComponent, SimpleComponent],
  imports: [BrowserModule, AppRoutingModule],
  providers: [],
  bootstrap: [AppComponent],
  entryComponents: [SimpleComponent]
})
export class AppModule {}

christian
  • 1,585
  • 1
  • 11
  • 13
  • I know what do yo say,but in my scenario I don't know how to do it,could you show me please? – Jesús Soto Mitjans Nov 05 '19 at 02:22
  • Hey Jesus, I updated the answer to include a working example. Let me know if it makes sense. – christian Nov 05 '19 at 03:05
  • Its works!,but I get an error on a line, I copied exactly your code just by replacing the word SimpleComponent with ConversacionComponent and the file paths. I get an error in the line of ViewChild ."Argument of type {read: ViewContainerRef } is not assignable to parameter of type {'read?:any;static;boolean'}Property 'static' is missing in type {read type of ViewContainerRef } but required in type {'read?:any;static;boolean'} " – Jesús Soto Mitjans Nov 05 '19 at 13:04
  • Why show me the line of View Child like an error specifically in {read: ViewContainerRef} ? – Jesús Soto Mitjans Nov 05 '19 at 13:22
  • hmm, are you importing `ViewChild` and `ViewContainerRef` from `@angular/core`? – christian Nov 05 '19 at 14:16
  • Yes,I can't send you a picture from here. From where I could write to you so I can send you an image or file so you can see better?. Sorry for such annoyance but I'm new to Angular and I don't know anyone who can help me. – Jesús Soto Mitjans Nov 05 '19 at 17:05
  • The application works as your example, but in the console I get exactly this error: ERROR in src / app / app.component.ts (91,39): error TS2345: Argument of type '{read: typeof ViewContainerRef; } 'is not assignable to parameter of type' {read ?: any; static: boolean; } '. Property 'static' is missing in type '{read: typeof ViewContainerRef; } 'but required in type' {read ?: any; static: boolean; } '. I have no idea why he points out that error on the line "@ViewChild ('componentsContainer', {read: ViewContainerRef}) container: ViewContainerRef;" – Jesús Soto Mitjans Nov 05 '19 at 18:11
  • Ah, Angular 8 requires the `static` field. Try this: `@ViewChild('componentsContainer', { read: ViewContainerRef, static: false }) container: ViewContainerRef;` Here are the docs: https://angular.io/api/core/ViewChild And here's a link to a helpful StackOverflow answer on static `true` vs. `false`: https://stackoverflow.com/a/56359612/7729975 – christian Nov 05 '19 at 18:56