在 Angular 结构指令中使用动态组件会产生额外的 HTML 标记。如何去除或更换它?
Using dynamic component within Angular structural directive produces extra HTML tag. How to remove or replace it?
我的项目中有相当复杂的基础设施,其中包含
- 主机组件
- 主机组件模板 (MyDir) 中使用的结构指令
- 结构指令 (MyComp) 中使用的另一个组件
简化版如下所示。
主机组件
@Component({
selector: 'my-app',
template: `
<table>
<tr *myDir="let item of data">
<td>{{item.text}}</td>
</tr>
</table>`
})
export class AppComponent {
data = [ { text: 'item 1' }, { text: 'item 2' } ];
}
结构指令
import { MyComp } from './myComp';
@Directive({ selector: '[myDir][myDirOf]' })
export class MyDir implements OnInit {
private data: any;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private resolver: ComponentFactoryResolver
) {
}
@Input() set myDirOf(data: any) {
this.data = data;
}
ngOnInit() {
const templateView = this.templateRef.createEmbeddedView({});
const compFactory = this.resolver.resolveComponentFactory(MyComp);
const componentRef = this.viewContainer.createComponent(
compFactory, undefined, this.viewContainer.injector, [templateView.rootNodes]
);
componentRef.instance.data = this.data;
componentRef.instance.template = this.templateRef;
}
}
结构指令的组件
@Component({
selector: '[my-comp]',
template: `
<tr><td>custom td</td></tr>
<ng-template *ngFor="let item of data"
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{ $implicit: item }"
></ng-template>`
})
export class MyComp {
public template: TemplateRef<any>;
public data: any;
}
输出为
custom td
item 1
item 2
除了标记
<table>
<div my-comp>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</div>
</table>
问题
我想从结果视图中删除中间 <div my-comp>
或至少用 <tbody>
替换它。要查看我准备的完整图片 Stackblitz DEMO,希望它会有所帮助...此外,这个示例可能很明显是人为的,但这就是我试图用最少的代码重现问题的原因。所以问题应该在给定的基础设施中有一个解决方案。
更新
@AlexG 找到了用 tbody
替换中间 div
的简单方法,stackblitz 演示一开始显示了一个不错的结果。但是当我尝试在本地将它应用到我的项目时,我遇到了新问题:浏览器在 table 的动态内容准备好呈现之前安排自己的 tbody
,这导致两个嵌套的 tbody
在最终视图中,根据 html 规格
似乎不一致
<table>
<tbody>
<tbody my-comp>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</tbody>
</tbody>
</table>
Stackblitz 演示没有这样的问题,只有 tbody my-comp
存在。但是我本地开发环境中的项目完全相同。所以我仍在努力寻找一种方法如何 删除 中间 my-comp
容器。
更新 2
demo已根据@markdBC建议的解决方案更新。
将您的组件的选择器从 [my-comp]
更改为 tbody [my-comp]
并且您将有一个 <tbody my-comp>
而不是 <div my-comp>
如果我理解正确的话这就足够了.
我的回答受到 Slim 对此处发现的类似问题的回答的启发:。
您可以通过
移除中间体<div my-comp>
- 正在
MyComp
组件内创建一个 TemplateRef
对象,表示 MyComp
组件的模板。
- 正在从结构指令访问此
TemplateRef
对象。
- 使用结构指令中的
TemplateRef
对象在视图容器内创建嵌入式视图。
生成的代码类似于:
MyComp
分量
@Component({
selector: "[my-comp]",
template: `
<ng-template #mytemplate>
<tr>
<td>custom td</td>
</tr>
<ng-template
*ngFor="let item of data"
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{ $implicit: item }"
></ng-template>
</ng-template>
`
})
export class MyComp {
public template: TemplateRef<any>;
public data: any;
@ViewChild("mytemplate", { static: true }) mytemplate: TemplateRef<any>;
}
MyDir
指令
export class MyDir implements OnInit {
private version: string;
private data: any;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private resolver: ComponentFactoryResolver
) {}
@Input() set myDirOf(data: any) {
this.data = data;
}
ngOnInit() {
const compFactory = this.resolver.resolveComponentFactory(MyComp);
const componentRef = compFactory.create(this.viewContainer.injector);
componentRef.instance.data = this.data;
componentRef.instance.template = this.templateRef;
this.viewContainer.createEmbeddedView(componentRef.instance.mytemplate);
}
}
结果 HTML 类似于:
<table>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</table>
我已经在 https://stackblitz.com/edit/table-no-div-wrapper 上准备了一个 StackBlitz 演示。
我的项目中有相当复杂的基础设施,其中包含
- 主机组件
- 主机组件模板 (MyDir) 中使用的结构指令
- 结构指令 (MyComp) 中使用的另一个组件
简化版如下所示。
主机组件
@Component({
selector: 'my-app',
template: `
<table>
<tr *myDir="let item of data">
<td>{{item.text}}</td>
</tr>
</table>`
})
export class AppComponent {
data = [ { text: 'item 1' }, { text: 'item 2' } ];
}
结构指令
import { MyComp } from './myComp';
@Directive({ selector: '[myDir][myDirOf]' })
export class MyDir implements OnInit {
private data: any;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private resolver: ComponentFactoryResolver
) {
}
@Input() set myDirOf(data: any) {
this.data = data;
}
ngOnInit() {
const templateView = this.templateRef.createEmbeddedView({});
const compFactory = this.resolver.resolveComponentFactory(MyComp);
const componentRef = this.viewContainer.createComponent(
compFactory, undefined, this.viewContainer.injector, [templateView.rootNodes]
);
componentRef.instance.data = this.data;
componentRef.instance.template = this.templateRef;
}
}
结构指令的组件
@Component({
selector: '[my-comp]',
template: `
<tr><td>custom td</td></tr>
<ng-template *ngFor="let item of data"
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{ $implicit: item }"
></ng-template>`
})
export class MyComp {
public template: TemplateRef<any>;
public data: any;
}
输出为
custom td
item 1
item 2
除了标记
<table>
<div my-comp>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</div>
</table>
问题
我想从结果视图中删除中间 <div my-comp>
或至少用 <tbody>
替换它。要查看我准备的完整图片 Stackblitz DEMO,希望它会有所帮助...此外,这个示例可能很明显是人为的,但这就是我试图用最少的代码重现问题的原因。所以问题应该在给定的基础设施中有一个解决方案。
更新
@AlexG 找到了用 tbody
替换中间 div
的简单方法,stackblitz 演示一开始显示了一个不错的结果。但是当我尝试在本地将它应用到我的项目时,我遇到了新问题:浏览器在 table 的动态内容准备好呈现之前安排自己的 tbody
,这导致两个嵌套的 tbody
在最终视图中,根据 html 规格
<table>
<tbody>
<tbody my-comp>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</tbody>
</tbody>
</table>
Stackblitz 演示没有这样的问题,只有 tbody my-comp
存在。但是我本地开发环境中的项目完全相同。所以我仍在努力寻找一种方法如何 删除 中间 my-comp
容器。
更新 2
demo已根据@markdBC建议的解决方案更新。
将您的组件的选择器从 [my-comp]
更改为 tbody [my-comp]
并且您将有一个 <tbody my-comp>
而不是 <div my-comp>
如果我理解正确的话这就足够了.
我的回答受到 Slim 对此处发现的类似问题的回答的启发:
您可以通过
移除中间体<div my-comp>
- 正在
MyComp
组件内创建一个TemplateRef
对象,表示MyComp
组件的模板。 - 正在从结构指令访问此
TemplateRef
对象。 - 使用结构指令中的
TemplateRef
对象在视图容器内创建嵌入式视图。
生成的代码类似于:
MyComp
分量
@Component({
selector: "[my-comp]",
template: `
<ng-template #mytemplate>
<tr>
<td>custom td</td>
</tr>
<ng-template
*ngFor="let item of data"
[ngTemplateOutlet]="template"
[ngTemplateOutletContext]="{ $implicit: item }"
></ng-template>
</ng-template>
`
})
export class MyComp {
public template: TemplateRef<any>;
public data: any;
@ViewChild("mytemplate", { static: true }) mytemplate: TemplateRef<any>;
}
MyDir
指令
export class MyDir implements OnInit {
private version: string;
private data: any;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef,
private resolver: ComponentFactoryResolver
) {}
@Input() set myDirOf(data: any) {
this.data = data;
}
ngOnInit() {
const compFactory = this.resolver.resolveComponentFactory(MyComp);
const componentRef = compFactory.create(this.viewContainer.injector);
componentRef.instance.data = this.data;
componentRef.instance.template = this.templateRef;
this.viewContainer.createEmbeddedView(componentRef.instance.mytemplate);
}
}
结果 HTML 类似于:
<table>
<tr><td>custom td</td></tr>
<tr><td>item 1</td></tr>
<tr><td>item 2</td></tr>
</table>
我已经在 https://stackblitz.com/edit/table-no-div-wrapper 上准备了一个 StackBlitz 演示。