Angular 2:删除组件中的包装 DOM 元素

Posted

技术标签:

【中文标题】Angular 2:删除组件中的包装 DOM 元素【英文标题】:Angular 2: Remove the wrapping DOM element in a component 【发布时间】:2018-02-03 16:36:06 【问题描述】:

我正在编写一个使用嵌套数据的 html 表格组件,因此输出可能如下所示:

<table>
  <tr><td>Parent</td></tr>
  <tr><td>Child 1</td></tr>
  <tr><td>Child 2</td></tr>
  <tr><td>Grandchild 1</td></tr>
</table>

我想使用递归组件来创建它,如下所示:

<table>
  <data-row *ngFor="let row of rows" [row]="row"></data-row>
</table>

数据行:

<tr><td>row.name</td></tr>
<data-row *ngFor="let child of row.children" [row]="child"></data-row>

但是,这会在表格行周围添加一个包装元素,这会破坏表格并且是无效的 HTML:

<table>
  <data-row>
    <tr>...</tr>
    <data-row><tr>...</tr></data-row>
  </data-row>
</table>

是否可以删除这个data-row 包装元素?

一种解决方案:

一种解决方案是使用&lt;tbody data-row...&gt;&lt;/tbody&gt;,这是我目前正在做的,但这会导致嵌套的tbody 元素违反W3C 规范

其他想法:

我尝试过使用ng-container,但似乎无法使用&lt;ng-container data-row...&gt;&lt;/ng-container&gt;,所以这不是首发。

我也考虑过放弃使用表格,但是使用 HTML 表格是允许将数据简单复制到电子表格中的唯一方法,这是必需的。

我考虑的最后一个选项是在生成表格之前将数据展平,但是,由于树可以随意展开和折叠,这会导致过度重新渲染或非常复杂的模型。

编辑:这是我当前解决方案的 Plunk(违反规范):http://plnkr.co/edit/LTex8GW4jfcH38D7RB4V?p=preview

【问题讨论】:

【参考方案1】:

发布另一个答案只是为了表明我在说什么......在此之后我会让你一个人呆着,保证。呵呵。

http://plnkr.co/edit/XcmEPd71m2w841oiL0CF?p=preview

此示例将所有内容呈现为平面结构,但保留了嵌套关系。每个项目都有一个对其父项的引用和一个由其子项组成的数组。

import Component, NgModule, VERSION, Input from '@angular/core'
import BrowserModule from '@angular/platform-browser'

@Component(
  selector: 'my-app',
  template: `
  <table *ngIf="tableReady">
    <tr *ngFor="let row of flatList" data-table-row [row]="row">  </tr>
  </table>
  `,
)
export class App 
  tableReady = false;
  table = [
    
      name: 'Parent',
      age: 70,
      children: [
        
          name: 'Child 1',
          age: 40,
          children: []
        ,
        
          name: 'Child 2',
          age: 30,
          children: [
            
              name: 'Grandchild 1',
              age: 10,
              children: []
            
          ]
        
      ]
    
  ];
  flatList = [];
  ngOnInit()    
    let flatten = (level : any[], parent? :any ) => 
     for (let item of level) 
        if (parent)  
          item['parent'] = parent; 
         
        this.flatList.push(item);
        if (item.children)  
          flatten(item.children, item);
        
      
      
     
    flatten(this.table);
    this.tableReady = true;
  


@Component(
  selector: '[data-table-row]',
  template: `
   <td>row.name</td><td>row.age</td>
  `
)
export class DataTableRowComponent 
  @Input() row: any;

【讨论】:

【参考方案2】:

只需使用类或属性作为组件的选择器并将其应用于表格行元素。

 @Component(
 selector: [data-row],

  <tr data-row> </tr>

 @Component( 
 selector:  .data-row,

 <tr class="data-row"></tr>

编辑-我只能使用子组件中的内容投影来使其工作,然后将 td 元素包含在父组件元素中。看这里 - https://plnkr.co/edit/CDU3Gn1Fg1sWLtrLCfxw?p=preview

如果你这样做,你可以使用 ContentChildren 查询所有行

 import  Component, ContentChildren, QueryList   from '@angular/core';
 import  DataRowComponent  from './wherever';

在你的组件中的某个地方...

 @ContentChildren(DataRowComponent) rows: QueryList<DataRowComponent>;

这将在 ngAfterContentInit 中定义

ngAfterContentInit()  
    console.log(this.rows);  <-- will print all the data from each component
 

注意 - 您还可以在自己的模板中拥有递归(这是一个词吗?)自己的组件。在数据行组件的模板中,您可以有任意数量的数据行组件。

【讨论】:

问题是您不能将一个tr 嵌套在另一个内部,而您的解决方案会导致此问题。我用 Plunk 更新了我的答案,显示了我想要实现的目标。 我不确定我上面提到的解决方案将如何导致嵌套 tr 元素,除非你按照我在底部的注释中所说的做了?不过,我并不是在为您的解决方案建议底部的注释,而只是将其作为一般提示。 在此处查看 plunker - plnkr.co/edit/CDU3Gn1Fg1sWLtrLCfxw?p=preview 如果您使用内容投影并将 td 元素包含在父模板中的组件内部而不是组件模板中,它似乎可以工作。这告诉我浏览器不会正确解释表格标签,除非所有必需的标签都在同一个模板中。所以尝试使用 ng-content 我已经按照您的建议编辑了我的示例。如您所见,该表不再有效! plnkr.co/edit/cDWaIo5kAaveSLCERdjM?p=preview 因为您使用 tr 作为组件的元素...但随后立即将 tr 元素再次放入子 component.template 作为其第一个元素。【参考方案3】:

如果你将行组件包装在一个 ng-container 中,你应该能够完成它

<tbody>
    <ng-container *ngFor="let row of rows; let i = index">
       <data-row  [row]="row"></data-row>
    </ng-container>
</tbody>

@Component(
  selector: 'my-app',
  template: `
  <table>
    <ng-container *ngFor="let row of table">
      <tbody data-table-row [row]="row"></tbody>
    </ng-container>
  </table>
  `,
)
export class App 
  table = [

    
      name: 'Parent', 
      children: [
        
          name: 'Child 1'
          children: []
        ,
        
          name: 'Child 2'
          children: [
            
              name: 'Grandchild 1'
              children: []
            
          ]
        
      ]
    
  ]


@Component(
  selector: 'tbody[data-table-row]',
  template: `
    <tr><td>row.name</td></tr>
    <tbody *ngFor="let child of row.children" data-table-row [row]="child"></tbody>
  `
)
export class DataTableRowComponent 
  @Input() row: any;

【讨论】:

这导致&lt;data-row&gt;&lt;tr&gt;...&lt;/tr&gt;&lt;/data-row&gt; 是无效的 HTML 它在data-row 内部,请参阅tbody 方法的示例:plnkr.co/edit/LTex8GW4jfcH38D7RB4V?p=preview 问题是,它不保存表格。见这里:plnkr.co/edit/qNqomFUr2h47YeuiosHs?p=preview @Josh,你能成功吗?我有一个带有 的解决方案和另一个带有 table 的解决方案,但还没有发布。如果你有兴趣,我会 还没有运气。我将不胜感激更多的解决方案

以上是关于Angular 2:删除组件中的包装 DOM 元素的主要内容,如果未能解决你的问题,请参考以下文章

Angular2 通知组件关于在 Angular 之外创建的 DOM 元素

如何将 DOM 附加到 Angular 2 组件并仍然获得封装样式

Angular 结构指令:用另一个组件包装宿主元素

角度 5 - 如何从 dom 中删除动态添加的组件

Angular 4 在声明组件模板时应用属性(不呈现包装元素)

Angular:如何在不删除子元素的情况下删除包装器(父元素)?