在 Angular 结构指令中使用动态组件会产生额外的 HTML 标记。如何删除或更换它?

Posted

技术标签:

【中文标题】在 Angular 结构指令中使用动态组件会产生额外的 HTML 标记。如何删除或更换它?【英文标题】:Using dynamic component within Angular structural directive produces extra HTML tag. How to remove or replace it? 【发布时间】:2020-03-08 15:32:12 【问题描述】:

我的项目中有相当复杂的基础设施,其中包含

主机组件 宿主组件模板 (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;

输出是

自定义 td 项目 1 第 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>

问题

我想从结果视图中删除中间的&lt;div my-comp&gt;,或者至少用&lt;tbody&gt; 替换它。要查看我准备的Stackblitz DEMO 的全貌,希望它会有所帮助......另外,很明显这个例子是人为的,但这就是我试图用最少的代码重现问题的目的。所以这个问题应该在给定的基础设施中有一个解决方案。

更新

@AlexG 找到了用tbody 替换中间div 的简单方法,stackblitz 演示一开始就显示出很好的结果。但是当我尝试将它应用到本地项目时,我遇到了新问题:浏览器在表格的动态内容准备好呈现之前安排了自己的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 建议的解决方案进行了更新。

【问题讨论】:

【参考方案1】:

我的回答灵感来自 Slim 对类似问题的回答:https://***.com/a/56887630/12962012。

您可以通过

删除中间的&lt;div my-comp&gt;
    MyComp 组件内创建一个代表MyComp 组件模板的TemplateRef 对象。 从结构指令访问此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 演示。

【讨论】:

看起来这是您对 SO 的第一个答案,它非常有帮助!请继续贡献,社区需要你!【参考方案2】:

将组件的选择器从[my-comp] 更改为tbody [my-comp],您将拥有&lt;tbody my-comp&gt; 而不是&lt;div my-comp&gt;,如果我理解正确的话就足够了。

【讨论】:

我更新了问题...似乎tbody 有问题,浏览器太聪明了,您的方法导致嵌套tbody 在真实环境中出现

以上是关于在 Angular 结构指令中使用动态组件会产生额外的 HTML 标记。如何删除或更换它?的主要内容,如果未能解决你的问题,请参考以下文章

Angular - 使用自定义指令加载多个组件返回未定义

Angular使用总结 --- 通过指令动态添加组件

Angular2-无法给元素的属性做双向绑定,除非这个属性是指令或者组件

在 Angular 9 中动态加载 *ngFor 内的组件

Angular 2:向动态创建的组件添加指令

从属性到结构,带你深入理解 Angular2 两大类型指令!