Angular - 在另一个组件中动态注入一个组件
Posted
技术标签:
【中文标题】Angular - 在另一个组件中动态注入一个组件【英文标题】:Angular - Dynamically inject a component in another component 【发布时间】: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 消息,并且可以看到一个新的 有人有办法解决我的问题吗? P.S.:我不想使用 TabContainer 组件数组来测试 createComponent 方法。 更新: 演示: https://stackblitz.com/edit/angular-imeh71?embed=1&file=src/app/app.component.ts 【问题讨论】:ngFor
解决这个问题呢?
我注入相同的组件仅用于测试目的。注入的组件可以由 app-component 中的任何逻辑创建
这是我可以让它工作的方法 -
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),它只是一种数据容器(它的模板几乎是空的,只是一个以上是关于Angular - 在另一个组件中动态注入一个组件的主要内容,如果未能解决你的问题,请参考以下文章
typescript 一个示例,显示动态地将模板注入Angular2组件的方式