2

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

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

问题

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

更新

@AlexG 找到了替换中间层的简单方法divtbody并且 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

演示已根据@markdBC 建议的解决方案进行了更新。

4

2 回答 2

4

我的回答受到 Slim 对此处发现的类似问题的回答的启发:https ://stackoverflow.com/a/56887630/12962012 。

<div my-comp>您可以通过以下方式删除中间体

  1. 在组件内部创建一个TemplateRef表示组件模板的对象。MyCompMyComp
  2. TemplateRef从结构指令访问此对象。
  3. 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 演示。

于 2020-02-25T18:48:52.353 回答
2

将组件的选择器从[my-comp]to更改为tbody [my-comp]a<tbody my-comp>而不是 a<div my-comp>如果我理解正确就足够了。

于 2019-11-12T21:39:49.947 回答