typescript Angular - 在另一个组件中动态注入一个组件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/51495787/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 05:32:50  来源:igfitidea点击:

Angular - Dynamically inject a component in another component

angulartypescript

提问by Justin C.

I'm developping a little UI components framework for my personal needs and for fun. I'm developping a Tab component and for testing purpose, I need to inject dynamically a component (TabContainerComponent) inside another component (TabComponent). Below, the code of my two components:

我正在为我的个人需求和乐趣开发一个小的 UI 组件框架。我正在开发一个 Tab 组件,为了测试目的,我需要在另一个组件 (TabComponent) 中动态注入一个组件 (TabContainerComponent)。下面,我的两个组件的代码:

tab.component.ts:

tab.component.ts:

import {Component, ContentChildren} from "@angular/core";
import {TabContainerComponent} from "./tabContainer.component";

@Component({
    selector: 'tab',
    templateUrl: 'tab.component.html'
})
export class TabComponent {

    @ContentChildren(TabContainerComponent)
    tabs: TabContainerComponent[];
}

tab.component.html:

tab.component.html:

<ul>
    <li *ngFor="let tab of tabs">{{ tab.title }}</li>
</ul>
<div>
    <div *ngFor="let tab of tabs">
        <ng-container *ngTemplateOutlet="tab.template"></ng-container>
    </div>
    <ng-content></ng-content>
</div>

tabContainer.component.ts:

tabContainer.component.ts:

import {Component, Input} from "@angular/core";

@Component({
    selector: 'tab-container',
    template: '<ng-container></ng-container>'
})
export class TabContainerComponent {

    @Input()
    title: string;

    @Input()
    template;
}

I used a ComponentFactoryResolver and a ComponentFactory to create dynamically my new component (TabContainerComponent), and inject it in a placeholder inside my other component (TabContainer), in the addTab method:

我使用了 ComponentFactoryResolver 和 ComponentFactory 来动态创建我的新组件 (TabContainerComponent),并在 addTab 方法中将其注入到我的其他组件 (TabContainer) 内的占位符中:

app.component.ts:

app.component.ts:

import {
    Component, ViewChild, ComponentFactoryResolver, ComponentFactory,
    ComponentRef, TemplateRef, ViewContainerRef
} from '@angular/core';
import {TabContainerComponent} from "./tabContainer.component";
import {TabComponent} from "./tab.component";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {

    title = 'app';

    @ViewChild(TabComponent)
    tab: TabComponent;

    @ViewChild('tabsPlaceholder', {read: ViewContainerRef})
    public tabsPlaceholder: ViewContainerRef;

    @ViewChild('newTab')
    newTab: TemplateRef<any>;

    constructor(private resolver: ComponentFactoryResolver) {
    }

    addTab(): void {
        let factory: ComponentFactory<TabContainerComponent> = this.resolver.resolveComponentFactory(TabContainerComponent);
        let tab: ComponentRef<TabContainerComponent> = this.tabsPlaceholder.createComponent(factory);
        tab.instance.title = "New tab";
        tab.instance.template = this.newTab;
        console.log('addTab() triggered');
    }
}

The addMethod is triggered by clicking on the "Add tab" button:

addMethod 是通过单击“添加选项卡”按钮触发的:

app.component.html:

app.component.html:

<button (click)="addTab()">Add tab</button>
<tab>
    <tab-container title="Tab 1" [template]="tab1"></tab-container>
    <tab-container title="Tab 2" [template]="tab2"></tab-container>
    <ng-container #tabsPlaceholder></ng-container>
</tab>
<ng-template #tab1>T1 template</ng-template>
<ng-template #tab2>T2 template</ng-template>
<ng-template #newTab>
    This is a new tab
</ng-template>

app.module.ts:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';


import { AppComponent } from './app.component';
import {TabContainerComponent} from "./tabContainer.component";
import {TabComponent} from "./tab.component";


@NgModule({
    declarations: [
        AppComponent,
        TabComponent,
        TabContainerComponent
    ],
    imports: [
        BrowserModule
    ],
    providers: [],
    bootstrap: [AppComponent],
    entryComponents: [
        TabContainerComponent
    ]
})
export class AppModule { }

When I click on the "Add tab" button, I'm able to see the console.log message and I'm able to see a new <tab-container> tag (but without any attribute, which is strange) inside the <tab> tag but Angular doesn't update the Tab component view (there is no <li> and <div> created). I tried also to check changes by implementing OnChanges interface in TabComponent class but without success.

当我单击“添加选项卡”按钮时,我能够看到 console.log 消息,并且能够看到 <tab-container> 标签(但没有任何属性,这很奇怪)在 < tab> 标签,但 Angular 不会更新 Tab 组件视图(没有创建 <li> 和 <div>)。我还尝试通过在 TabComponent 类中实现 OnChanges 接口来检查更改,但没有成功。

Is anyone have an idea to solve my problem ?

有没有人有解决我的问题的想法?

P.S.: I don't want to use an array of TabContainer components in order to test the createComponent method.

PS:我不想使用 TabContainer 组件数组来测试 createComponent 方法。

Update:

更新:

Demo:

演示:

https://stackblitz.com/edit/angular-imeh71?embed=1&file=src/app/app.component.ts

https://stackblitz.com/edit/angular-imeh71?embed=1&file=src/app/app.component.ts

回答by byj

Here is the way I could make it work -

这是我可以让它工作的方式 -

Tab.component.ts

Tab.component.ts

Changed "tabs" from TabContainerComponent array to QueryList.

将“选项卡”从 TabContainerComponent 数组更改为 QueryList。

import { Component, ContentChildren, QueryList } from '@angular/core';
import { TabContainerComponent } from '../tab-container/tab-container.component';

@Component({
  selector: 'app-tab',
  templateUrl: 'tab.component.html'
})
export class TabComponent {
  @ContentChildren(TabContainerComponent)
  tabs: QueryList<TabContainerComponent>;

  constructor() {}
}

Added a new template in app.component.html

app.component.html 中添加了一个新模板

<ng-template #tabContainerTemplate>
  <app-tab-container title="New Tab" [template]="newTab"></app-tab-container>
</ng-template>

app.component.ts

app.component.ts

import {
  Component,
  ViewChild,
  TemplateRef,
  ViewContainerRef,
  AfterViewInit,
  ViewChildren,
  QueryList,
  ChangeDetectorRef
} from '@angular/core';
import { TabContainerComponent } from './tab-container/tab-container.component';
import { TabComponent } from './tab/tab.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  title = 'app';
  changeId: string;
  @ViewChild(TabComponent) tab: TabComponent;
  @ViewChild('tabsPlaceholder', { read: ViewContainerRef })
  public tabsPlaceholder: ViewContainerRef;
  @ViewChild('tabContainerTemplate', { read: TemplateRef })
  tabContainerTemplate: TemplateRef<null>;
  @ViewChildren(TabContainerComponent)
  tabList: QueryList<TabContainerComponent>;

  constructor(private changeDetector: ChangeDetectorRef) {}

  ngAfterViewInit() {}

  addTab(): void {
    this.tabsPlaceholder.createEmbeddedView(this.tabContainerTemplate);
    this.tab.tabs = this.tabList;
    this.changeDetector.detectChanges();
    console.log('addTab() triggered');
  }
}

Added a ViewChildren query for TabContainerComponent. In addTab() used createEmbeddedView to add new tab container component.

为 TabContainerComponent 添加了一个 ViewChildren 查询。在 addTab() 中使用 createEmbeddedView 添加新的选项卡容器组件。

I thought that "ContentChildren" query in TabComponent should be updated by the newly added component but it doesn't. I have tried to subscribe to "changes" for the query list in TabCompoent but it doesn't get triggered.

我认为 TabComponent 中的“ContentChildren”查询应该由新添加的组件更新,但事实并非如此。我试图订阅 TabCompoent 中查询列表的“更改”,但它没有被触发。

But I observed that "ViewChildren" query in AppComponent got updated every time a new component is added. So I have assigned updated QueryList of app component to the QueryList of TabComponent.

但是我观察到每次添加新组件时 AppComponent 中的“ViewChildren”查询都会更新。因此,我已将应用组件的更新 QueryList 分配给 TabComponent 的 QueryList。

The working demo is available here

工作演示可在此处获得

回答by Chellappan ?

I have modified your code check this it is showing in view as you expected.

我已经修改了您的代码检查它是否按您的预期显示在视图中。

StackblitzDemo

Stackblitz演示

For Dynamic component Creating You need to create Embedded View using templateRef.

对于动态组件创建您需要使用 templateRef 创建嵌入式视图。

View Container provides API to create, manipulate and remove dynamic views.

View Container 提供 API 来创建、操作和删除动态视图。

For More Info About Dynamic Component Manipulation check this: https://blog.angularindepth.com/working-with-dom-in-angular-unexpected-consequences-and-optimization-techniques-682ac09f6866

有关动态组件操作的更多信息,请查看:https: //blog.angularindepth.com/working-with-dom-in-angular-unexpected-consequences-and-optimization-techniques-682ac09f6866

  import {
    Component, ViewChild, ComponentFactoryResolver, ComponentFactory,
    ComponentRef, TemplateRef, ViewContainerRef,AfterViewInit
} from '@angular/core';
import {TabContainerComponent} from "./hello.component";
import {TapComponent} from "./tap/tap.component";

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {

    title = 'app';
   @ViewChild('vc',{read:ViewContainerRef}) vc:ViewContainerRef;
    @ViewChild(TapComponent)
    tab: TapComponent;

    @ViewChild('tabsPlaceholder', {read: ViewContainerRef})
    public tabsPlaceholder: ViewContainerRef;

    @ViewChild('newTab')
    newTab: TemplateRef<any>;

    constructor(private resolver: ComponentFactoryResolver) {
    }

  ngAfterViewInit(){


  }

    addTab(): void {
        let factory: ComponentFactory<TabContainerComponent> = this.resolver.resolveComponentFactory(TabContainerComponent);
        let tab: ComponentRef<TabContainerComponent> = this.tabsPlaceholder.createComponent(factory);
        tab.instance.title = "New tab";
        tab.instance.template = this.newTab;
        this.vc.createEmbeddedView(this.newTab);
        console.log('addTab() triggered');
    }
}