Ajouter et enlever des composants de façon dynamique dans un angle
Le cours officiel docs montre comment dynamiquement modifier composants à l'intérieur de<ng-template>
balise. https://angular.io/guide/dynamic-component-loader
Ce que je veux réaliser est, disons que j'ai 3 composants: header
,section
et footer
avec les sélecteurs suivants:
<app-header>
<app-section>
<app-footer>
Et puis il y a 6 boutons ajouter ou supprimer chaque élément: Add Header
,Add Section
, et Add Footer
et quand je clique sur Add Header
, la page ajoutera <app-header>
à la page qui le rend, donc la page contiendra:
<app-header>
Et puis si je clique sur Add Section
deux fois, la page contient maintenant:
<app-header>
<app-section>
<app-section>
Et si je clique sur Add Footer
, la page contient maintenant tous ces composants:
<app-header>
<app-section>
<app-section>
<app-footer>
est-il possible de réaliser ceci en angle? Notez que ngFor
n'est pas la solution que je cherche, car elle permet seulement d'ajouter le même composants, pas différents éléments d'une page.
EDIT: ngFor et nggfor ne sont pas la solution que je recherche car les modèles sont déjà prédéfinis. Ce que je cherche est quelque chose comme une pile de composants ou un tableau de composants où nous pouvons ajouter, supprimer et changer n'importe quel index du tableau facilement.
EDIT 2: pour être plus clair, donnons un autre exemple de pourquoi ngFor ne fonctionne pas. Disons que nous avons le suivant composants:
<app-header>
<app-introduction>
<app-camera>
<app-editor>
<app-footer>
Voici maintenant un nouveau composant,<app-description>
, que l'utilisateur souhaite insérer dans entre et <app-editor>
. ngFor ne fonctionne que s'il y a un seul composant que je veux boucler encore et encore. Mais pour différents composants, ngFor échoue ici.
2 réponses
ce que vous essayez de réaliser peut être fait en créant des composants dynamiquement en utilisant le ComponentFactoryResolver
et puis les injecter dans un ViewContainerRef
. Une façon de faire cela dynamiquement est de passer la classe du component comme argument de votre fonction qui va créer et injecter le component.
Voir l'exemple ci-dessous:
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);
}
}
}
Notes:
Si vous voulez faire plus facile de retirer les composants plus tard, vous pouvez garder une trace d'eux dans un variable locale, voir
this.components
. Alternativement vous pouvez boucler tous les éléments à l'intérieur duViewContainerRef
.vous devez enregistrer votre composant comme un composant d'entrée. Dans votre définition de module, enregistrez votre component comme entryComponent (
entryComponents: [DraggableComponent]
).
exemple D'exécution: https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5
Pour en savoir plus information: https://angular.io/guide/dynamic-component-loader
j'ai créé des composants parent et enfant pour montrer la méthode add and remove. Parent component crée les composants enfant dynamiquement et les supprime.
Cliquez pour la démo
Composant Parent
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);
}
}
Composant Enfant
@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);
}
ajouter des références au app.module.ts
@NgModule({
declarations: [
ParentComponent,
ChildComponent
],
imports: [
//if using routing then add like so
RouterModule.forRoot([
{ path: '', component: ParentComponent }
]),
],
entryComponents: [
ChildComponent,
],