【问题标题】:Angular - Dynamically inject a component in another componentAngular - 在另一个组件中动态注入一个组件
【发布时间】:2018-12-31 21:48:42
【问题描述】:

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

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:

<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:

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

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

    @Input()
    title: string;

    @Input()
    template;
}

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

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

addMethod是通过点击“添加标签”按钮触发的:

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:

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 { }

当我单击“添加选项卡”按钮时,我可以看到 console.log 消息,并且可以看到一个新的 标记(但没有任何属性,这很奇怪)在 标记内,但 Angular 不会更新 Tab 组件视图(没有创建

  • 和
    )。我还尝试通过在 TabComponent 类中实现 OnChanges 接口来检查更改,但没有成功。

    有人有办法解决我的问题吗?

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

    更新:

    演示:

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

    【问题讨论】:

    • 鉴于您每次都会注入相同的组件,为什么不用ngFor 解决这个问题呢?
    • 我注入相同的组件仅用于测试目的。注入的组件可以由 app-component 中的任何逻辑创建
  • 标签: angular typescript


    【解决方案1】:

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

    Tab.component.ts

    将“选项卡”从 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() {}
    }
    

    在 app.component.html

    中添加了新模板
    <ng-template #tabContainerTemplate>
      <app-tab-container title="New Tab" [template]="newTab"></app-tab-container>
    </ng-template>
    

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

    为 TabContainerComponent 添加了 ViewChildren 查询。在 addTab() 中使用 createEmbeddedView 添加新的标签容器组件。

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

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

    工作演示可用here

    【讨论】:

    • 感谢您的解决方案。它按预期工作。关键是通过替换 AppComponent 中的选项来强制更新 TabComponent 的选项卡属性,并且不要忘记使用 ChangeDetectorRef 检测更改(否则,每次单击按钮时都不会更新视图)。在我看来,这不是更优雅的解决方案,但它解决了我的问题,而且它似乎是唯一现有的解决方案......
    • 老实说,我不想强​​制从 AppComponent 更新到 TabComponent。我期望每次添加新组件后都会更新 ContentChildren 查询,但它没有得到更新。我在 TabComponent 中尝试了其他一些方法,例如在 nOnAfterContentInit() 中订阅 tabList 更改(this.tabs.changes.subscribe()),但没有任何效果。所以最后我不得不强制从 App Component 分配给 TabComponent。
    【解决方案2】:

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

    Stackblitz演示

    动态组件创建需要使用templateRef创建Embedded View。

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

    有关动态组件操作的更多信息,请查看: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');
        }
    }
    

    【讨论】:

    • @Chellappan...为什么新标签没有在按钮点击时添加?
    • @Chellappan 添加选项卡按钮的目的是添加一个“新选项卡”(一个
    • 和一个
      就像其他选项卡一样)
  • 这是因为我们没有在每次点击后创建新的嵌入式视图现在它工作正常检查
  • 目的不是在Tab组件之后创建视图。我想注入一个新组件(TabContainer),它只是一种数据容器(它的模板几乎是空的,只是一个 因为模板不能为空)。这个注入的组件应该被 Tab 组件检测到,并且这个最新的应该更新它的视图以便创建相应的
  • 和
    。
  • 如果你想显示动态创建的组件,你需要使用视图,除非它会产生不需要的结果。检查你会理解的博客
  • 猜你喜欢
    相关资源
    最近更新 更多
    热门标签