web-dev-qa-db-ja.com

Angularのコンポーネントの動的な追加と削除

現在の公式ドキュメントは、<ng-template>を動的にchangeコンポーネントwithinする方法のみを示しています鬼ごっこ。 https://angular.io/guide/dynamic-component-loader

私が達成したいのは、次のセレクターを備えたheadersection、およびfooterの3つのコンポーネントがあるとします。

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

そして、各コンポーネントを追加または削除する6つのボタンがあります:Add HeaderAdd SectionAdd Footer

Add Headerをクリックすると、ページは<app-header>をレンダリングするページに追加するため、ページには以下が含まれます。

<app-header>

そして、Add Sectionを2回クリックすると、ページに次が含まれます。

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

そして、Add Footerをクリックすると、これらのすべてのコンポーネントがページに含まれるようになります。

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

これをAngularで達成することは可能ですか? ngForは、ページに異なるコンポーネントではなく、同じコンポーネントのみを追加できるため、私が探しているソリューションではないことに注意してください。

編集:ngIfとngForは、テンプレートが既に事前に決定されているため、私が探しているソリューションではありません。私が探しているのは、配列のインデックスを簡単に追加、削除、変更できるコンポーネントのスタックやコンポーネントの配列のようなものです。

編集2:より明確にするために、ngForが機能しない別の例を見てみましょう。次のコンポーネントがあるとしましょう:

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

ここに、ユーザーが<app-description>の間に挿入したい新しいコンポーネント<app-editor>があります。 ngForは、繰り返しループしたい同じコンポーネントが1つある場合にのみ機能します。ただし、異なるコンポーネントの場合、ngForはここで失敗します。

27

達成しようとしていることは、ComponentFactoryResolverを使用してコンポーネントを動的に作成し、それらをViewContainerRefに挿入することで実行できます。これを動的に行う1つの方法は、コンポーネントのクラスを、コンポーネントを作成して注入する関数の引数として渡すことです。

以下の例を参照してください。

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

ノート:

  1. 後でコンポーネントを簡単に削除したい場合は、ローカル変数でそれらを追跡できます。this.componentsを参照してください。または、ViewContainerRef内のすべての要素をループできます。

  2. コンポーネントをエントリコンポーネントとして登録する必要があります。モジュール定義で、コンポーネントをentryComponent(entryComponents: [DraggableComponent])として登録します。

実行例: https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5

詳細: https://angular.io/guide/dynamic-component-loader

39
Ash Belmokadem

動的な追加および削除プロセスを示すデモを作成しました。親コンポーネントは、子コンポーネントを動的に作成し、削除します。

デモ用にクリック

親コンポーネント

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

子コンポーネント

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

app.module.tsへの参照を追加します

@NgModule({
  declarations: [

    ParentComponent,
    ChildComponent

  ],
  imports: [

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

  ],
  entryComponents: [

    ChildComponent,  

  ],
11
WasiF