img - title html
AGREGAR y QUITAR componentes dinĂ¡micamente en angular (2)
Los documentos oficiales actuales solo muestran cómo
cambiar
dinámicamente componentes
dentro de
una etiqueta
<ng-template>
.
https://angular.io/guide/dynamic-component-loader
Lo que quiero lograr es, digamos que tengo 3 componentes:
header
,
section
y
footer
con los siguientes selectores:
<app-header>
<app-section>
<app-footer>
Y luego hay 6 botones que agregarán o eliminarán cada componente:
Add Header
,
Add Section
y
Add Footer
y cuando hago clic en
Add Header
, la página agregará
<app-header>
a la página que lo muestra, por lo que la página contendrá:
<app-header>
Y luego, si hago clic en
Add Section
dos veces, la página ahora contendrá:
<app-header>
<app-section>
<app-section>
Y si hago clic en
Add Footer
, la página ahora contendrá todos estos componentes:
<app-header>
<app-section>
<app-section>
<app-footer>
¿Es posible lograr esto en Angular?
Tenga en cuenta que
ngFor
no es la solución que estoy buscando, ya que solo permite agregar los mismos componentes, no componentes diferentes a una página.
EDITAR: ngIf y ngFor no es la solución que estoy buscando, ya que las plantillas ya están predeterminadas. Lo que estoy buscando es algo así como una pila de componentes o una matriz de componentes donde podamos agregar, eliminar y cambiar cualquier índice de la matriz fácilmente.
EDIT 2: para que quede más claro, tengamos otro ejemplo de por qué ngFor no funciona. Digamos que tenemos los siguientes componentes:
<app-header>
<app-introduction>
<app-camera>
<app-editor>
<app-footer>
Ahora aquí viene un nuevo componente,
<app-description>
, que el usuario quiere insertar entre y
<app-editor>
.
ngFor solo funciona si hay un mismo componente que quiero repetir una y otra vez.
Pero para diferentes componentes, ngFor falla aquí.
He creado una demostración para mostrar el proceso dinámico de agregar y quitar. El componente principal crea los componentes secundarios dinámicamente y los elimina.
Haga clic para ver la demostración
Componente principal
import { ComponentRef, ComponentFactoryResolver, ViewContainerRef, ViewChild, Component } from "@angular/core";
@Component({
selector: ''parent'',
template: `
<button type="button" (click)="createComponent()">
Create Child
</button>
<div>
<ng-template #viewContainerRef></ng-template>
</div>
`
})
export class ParentComponent implements myinterface {
@ViewChild(''viewContainerRef'', { read: ViewContainerRef }) VCR: ViewContainerRef;
//manually indexing the child components for better removal
//although there is by-default indexing but it is being avoid for now
//so index is a unique property here to identify each component individually.
index: number = 0;
// to store references of dynamically created components
componentsReferences = [];
constructor(private CFR: ComponentFactoryResolver) {
}
createComponent() {
let componentFactory = this.CFR.resolveComponentFactory(ChildComponent);
let componentRef: ComponentRef<ChildComponent> = this.VCR.createComponent(componentFactory);
let currentComponent = componentRef.instance;
currentComponent.selfRef = currentComponent;
currentComponent.index = ++this.index;
// prividing parent Component reference to get access to parent class methods
currentComponent.compInteraction = this;
// add reference for newly created component
this.componentsReferences.push(componentRef);
}
remove(index: number) {
if (this.VCR.length < 1)
return;
let componentRef = this.componentsReferences.filter(x => x.instance.index == index)[0];
let component: ChildComponent = <ChildComponent>componentRef.instance;
let vcrIndex: number = this.VCR.indexOf(componentRef)
// removing component from container
this.VCR.remove(vcrIndex);
this.componentsReferences = this.componentsReferences.filter(x => x.instance.index !== index);
}
}
Componente hijo
@Component({
selector: ''child'',
template: `
<div>
<h1 (click)="removeMe(index)">I am a Child, click to Remove</h1>
</div>
`
})
export class ChildComponent {
public index: number;
public selfRef: ChildComponent;
//interface for Parent-Child interaction
public compInteraction: myinterface;
constructor() {
}
removeMe(index) {
this.compInteraction.remove(index)
}
}
// Interface
export interface myinterface {
remove(index: number);
}
agregar referencias a app.module.ts
@NgModule({
declarations: [
ParentComponent,
ChildComponent
],
imports: [
//if using routing then add like so
RouterModule.forRoot([
{ path: '''', component: ParentComponent }
]),
],
entryComponents: [
ChildComponent,
],
Lo que está tratando de lograr se puede hacer creando componentes dinámicamente usando
ComponentFactoryResolver
y luego inyectándolos en
ViewContainerRef
.
Una forma de hacer esto dinámicamente es pasar la clase del componente como un argumento de su función que creará e inyectará el componente.
Ver ejemplo a continuación:
import {
Component,
ComponentFactoryResolver, Type,
ViewChild,
ViewContainerRef
} from ''@angular/core'';
// Example component (can be any component e.g. app-header app-section)
import { DraggableComponent } from ''./components/draggable/draggable.component'';
@Component({
selector: ''app-root'',
template: `
<!-- Pass the component class as an argument to add and remove based on the component class -->
<button (click)="addComponent(draggableComponentClass)">Add</button>
<button (click)="removeComponent(draggableComponentClass)">Remove</button>
<div>
<!-- Use ng-template to ensure that the generated components end up in the right place -->
<ng-template #container>
</ng-template>
</div>
`
})
export class AppComponent {
@ViewChild(''container'', {read: ViewContainerRef}) container: ViewContainerRef;
// Keep track of list of generated components for removal purposes
components = [];
// Expose class so that it can be used in the template
draggableComponentClass = DraggableComponent;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
}
addComponent(componentClass: Type<any>) {
// Create component dynamically inside the ng-template
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentClass);
const component = this.container.createComponent(componentFactory);
// Push the component so that we can keep track of which components are created
this.components.push(component);
}
removeComponent(componentClass: Type<any>) {
// Find the component
const component = this.components.find((component) => component.instance instanceof componentClass);
const componentIndex = this.components.indexOf(component);
if (componentIndex !== -1) {
// Remove component from both view and array
this.container.remove(this.container.indexOf(component));
this.components.splice(componentIndex, 1);
}
}
}
Notas:
-
Si desea facilitar la eliminación de los componentes más adelante, puede realizar un seguimiento de ellos en una variable local, consulte
this.components
. Alternativamente, puede recorrer todos los elementos dentro deViewContainerRef
. -
Debe registrar su componente como componente de entrada. En la definición de su módulo, registre su componente como entryComponent (
entryComponents: [DraggableComponent]
).
Ejemplo de ejecución: https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5
Para más información: https://angular.io/guide/dynamic-component-loader