web-dev-qa-db-fra.com

AJOUTER et RETIRER dynamiquement les composants dans Angular

La documentation officielle actuelle indique uniquement comment modifier dynamiquement les composants dans et <ng-template> tag. https://angular.io/guide/dynamic-component-loader

Ce que je veux réaliser, c’est que, disons, j’ai 3 composantes: header, section et footer avec les sélecteurs suivants:

<app-header>
<app-section>
<app-footer>

Et puis il y a 6 boutons qui ajouteront ou supprimeront chaque composant: Add Header, Add Section, et Add Footer

et quand je clique sur Add Header, la page ajoutera <app-header> à la page qui le rend, ainsi la page contiendra:

<app-header>

Et puis si je clique sur Add Section _ deux fois, la page contiendra désormais:

<app-header>
<app-section>
<app-section>

Et si je clique sur Add Footer, la page contiendra désormais tous ces composants:

<app-header>
<app-section>
<app-section>
<app-footer>

Est-il possible d'y parvenir en angulaire? Notez que ngFor n'est pas la solution que je recherche, car cela permet uniquement d'ajouter les mêmes composants, pas des composants différents à une page.

EDIT: ngIf et ngFor ne sont pas la solution que je cherche car les modèles sont déjà prédéterminés. Ce que je recherche, c'est quelque chose comme une pile de composants ou un tableau de composants où nous pouvons facilement ajouter, supprimer et modifier tout index du tableau.

EDIT 2: Pour plus de clarté, donnons un autre exemple de la raison pour laquelle ngFor ne fonctionne pas. Disons que nous avons les composants suivants:

<app-header>
<app-introduction>
<app-camera>
<app-editor>
<app-footer>

Maintenant, voici un nouveau composant, <app-description>, que l'utilisateur veut insérer entre et <app-editor>. ngFor ne fonctionne que s’il existe un même composant que je souhaite lire encore et encore. Mais pour différents composants, ngFor échoue ici.

27

Ce que vous essayez d’atteindre peut être fait en créant des composants de manière dynamique en utilisant ComponentFactoryResolver, puis en les injectant dans un ViewContainerRef. Une façon de le faire de manière dynamique consiste à passer la classe du composant en tant qu'argument de votre fonction qui créera et injectera le composant.

Voir 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);
    }
  }
}

Remarques:

  1. Si vous souhaitez faciliter la suppression ultérieure des composants, vous pouvez les suivre dans une variable locale, voir this.components. Sinon, vous pouvez parcourir tous les éléments à l'intérieur de ViewContainerRef.

  2. Vous devez enregistrer votre composant en tant que composant d'entrée. Dans votre définition de module, enregistrez votre composant en tant que entryComponent (entryComponents: [DraggableComponent]).

Exemple en cours d'exécution: https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5

Pour plus d'informations: https://angular.io/guide/dynamic-component-loader

39
Ash Belmokadem

J'ai créé une démo pour montrer le processus dynamique d'ajout et de suppression. Le composant parent crée les composants enfants de manière dynamique 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 à app.module.ts

@NgModule({
  declarations: [

    ParentComponent,
    ChildComponent

  ],
  imports: [

    //if using routing then add like so
    RouterModule.forRoot([
      { path: '', component: ParentComponent }
    ]),

  ],
  entryComponents: [

    ChildComponent,  

  ],
11
WasiF