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.

18
demandé sur Marcellino Corleone 2017-07-06 07:36:12

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:

  1. 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 du ViewContainerRef.

  2. 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

27
répondu Ash Belmokadem 2017-07-06 09:28:41

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,  

  ],
3
répondu WasiF 2018-09-13 11:09:34