Angular2:渲染一个没有包装标签的组件

IT技术 javascript angular
2021-02-04 23:47:37

我正在努力寻找一种方法来做到这一点。在父组件中,模板描述了 atable及其thead元素,但将渲染委托tbody给另一个组件,如下所示:

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Time</th>
    </tr>
  </thead>
  <tbody *ngFor="let entry of getEntries()">
    <my-result [entry]="entry"></my-result>
  </tbody>
</table>

每个 myResult 组件呈现自己的tr标签,基本上是这样的:

<tr>
  <td>{{ entry.name }}</td>
  <td>{{ entry.time }}</td>
</tr>

我没有将它直接放在父组件中(避免需要 myResult 组件)的原因是 myResult 组件实际上比这里显示的更复杂,所以我想把它的行为放在一个单独的组件和文件中。

生成的 DOM 看起来很糟糕。我相信这是因为它是无效的,因为它tbody只能包含tr元素(参见 MDN),但我生成的(简化的)DOM 是:

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Time</th>
    </tr>
  </thead>
  <tbody>
    <my-result>
      <tr>
        <td>Bob</td>
        <td>128</td>
      </tr>
    </my-result>
  </tbody>
  <tbody>
    <my-result>
      <tr>
        <td>Lisa</td>
        <td>333</td>
      </tr>
    </my-result>
  </tbody>
</table>

有什么办法可以让我们渲染同样的东西,但没有包装<my-result>标签,同时仍然使用一个组件来单独负责渲染表格行?

我已经看过ng-content, DynamicComponentLoader, the ViewContainerRef,但据我所知,他们似乎没有提供解决方案。

6个回答

您可以使用属性选择器

@Component({
  selector: '[myTd]'
  ...
})

然后像这样使用它

<td myTd></td>
@AviadP。非常感谢...我知道需要进行一个小改动,我对此失去了理智。所以而不是这个:<ul class="nav navbar-nav"> <li-navigation [navItems]="menuItems"></li-navigation> </ul>我使用了这个(在将 li-navigation 更改为属性选择器之后)<ul class="nav navbar-nav" li-navigation [navItems]="menuItems"></ul>
2021-03-13 23:47:37
如果您试图增加材料组件,则此答案不起作用,例如 <mat-toolbar my-toolbar-rows> 会抱怨您只能有一个组件选择器而不是多个。我可以去 <my-toolbar-component> 但是它把 mat-toolbar 放在一个 div 中
2021-03-18 23:47:37
您不能在 ng-container 上设置属性组件,因为它已从 DOM 中删除。这就是为什么您需要在实际的 HTML 标签上设置它的原因。
2021-03-21 23:47:37
此解决方案适用于简单的情况(如 OP),但如果您需要创建一个my-results内部可以生成更多的解决方案my-results,那么您需要ViewContainerRef(检查下面@Slim 的答案)。这个解决方案在这种情况下不起作用的原因是第一个属性选择器进入 a tbody,但是内部选择器在哪里?它不能在 a 中tr,也不能将 atbody放在 another 中tbody
2021-03-24 23:47:37
setAttribute,不是我的代码。但我已经想通了,我需要在我的模板中使用实际的顶级标签作为我的组件的标签,而不是ng-container新的工作用法<ul smMenu class="nav navbar-nav" [submenus]="root?.Submenus" [title]="root?.Title"></ul>
2021-03-27 23:47:37

您需要“ViewContainerRef”并在 my-result 组件中执行以下操作:

html:

<ng-template #template>
    <tr>
       <td>Lisa</td>
       <td>333</td>
    </tr>
 </ng-template>

ts:

@ViewChild('template') template;


  constructor(
    private viewContainerRef: ViewContainerRef
  ) { }

  ngOnInit() {
    this.viewContainerRef.createEmbeddedView(this.template);
  }
这奏效了。我有一个情况,我正在使用 css display:grid 并且你不能让它仍然有 <my-result> 标签作为父元素中的第一个元素,而 my-result 的所有子元素都作为 my-result 的兄弟元素呈现. 问题是一个额外的幽​​灵元素仍然打破了网格 7 列“grid-template-columns:repeat(7, 1fr);” 它正在渲染 8 个元素。我的结果和 7 个列标题的 1 个幽灵占位符。解决这个问题的方法是通过将 <my-result style="display:none"> 放在您的标签中来简单地隐藏它。在那之后,css 网格系统就像一个魅力。
2021-03-15 23:47:37
如果有人在 this.viewContainerRef 中有错误,请将代码从 ngOnInit 移至 ngAfterViewInit。
2021-03-15 23:47:37
该解决方案甚至适用于更复杂的情况,my-result需要能够创建新的my-result兄弟姐妹。因此,假设您有一个层次结构,my-result其中每一行都可以有“子”行。在这种情况下,使用属性选择器是行不通的,因为第一选择进入的tbody,但第二个不能在内部去tbody,也不在tr
2021-03-25 23:47:37
当我使用它时,如何避免出现 ExpressionChangedAfterItHasBeenCheckedError?
2021-04-01 23:47:37
奇迹般有效!谢谢你。我在 Angular 8 中使用了以下内容:@ViewChild('template', {static: true}) template;
2021-04-06 23:47:37

你可以尝试使用新的 css display: contents

这是我的工具栏 scss:

:host {
  display: contents;
}

:host-context(.is-mobile) .toolbar {
  position: fixed;
  /* Make sure the toolbar will stay on top of the content as it scrolls past. */
  z-index: 2;
}

h1.app-name {
  margin-left: 8px;
}

和 html:

<mat-toolbar color="primary" class="toolbar">
  <button mat-icon-button (click)="toggle.emit()">
    <mat-icon>menu</mat-icon>
  </button>
  <img src="/assets/icons/favicon.png">
  <h1 class="app-name">@robertking Dashboard</h1>
</mat-toolbar>

并在使用中:

<navigation-toolbar (toggle)="snav.toggle()"></navigation-toolbar>

属性选择器是解决这个问题的最好方法。

所以在你的情况下:

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Time</th>
    </tr>
  </thead>
  <tbody my-results>
  </tbody>
</table>

我的结果 ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'my-results, [my-results]',
  templateUrl: './my-results.component.html',
  styleUrls: ['./my-results.component.css']
})
export class MyResultsComponent implements OnInit {

  entries: Array<any> = [
    { name: 'Entry One', time: '10:00'},
    { name: 'Entry Two', time: '10:05 '},
    { name: 'Entry Three', time: '10:10'},
  ];

  constructor() { }

  ngOnInit() {
  }

}

我的结果 html

  <tr my-result [entry]="entry" *ngFor="let entry of entries"><tr>

我的结果 ts

import { Component, OnInit, Input } from '@angular/core';

@Component({
  selector: '[my-result]',
  templateUrl: './my-result.component.html',
  styleUrls: ['./my-result.component.css']
})
export class MyResultComponent implements OnInit {

  @Input() entry: any;

  constructor() { }

  ngOnInit() {
  }

}

我的结果 html

  <td>{{ entry.name }}</td>
  <td>{{ entry.time }}</td>

查看工作堆栈闪电战:https ://stackblitz.com/edit/angular-xbbegx

selector: 'my-results, [my-results]',那么我就可以在 HTML 中使用 my-results 作为标签的属性。这是正确答案。它适用于 Angular 8.2。
2021-04-11 23:47:37

在您的元素上使用此指令

@Directive({
   selector: '[remove-wrapper]'
})
export class RemoveWrapperDirective {
   constructor(private el: ElementRef) {
       const parentElement = el.nativeElement.parentElement;
       const element = el.nativeElement;
       parentElement.removeChild(element);
       parentElement.parentNode.insertBefore(element, parentElement.nextSibling);
       parentElement.parentNode.removeChild(parentElement);
   }
}

用法示例:

<div class="card" remove-wrapper>
   This is my card component
</div>

在父 html 中,您像往常一样调用 card 元素,例如:

<div class="cards-container">
   <card></card>
</div>

输出将是:

<div class="cards-container">
   <div class="card" remove-wrapper>
      This is my card component
   </div>
</div>
这完全删除了组件
2021-03-16 23:47:37
@BenWinding不会, 因为 Angular 在内部使用通常称为视图组件视图的数据结构来表示组件,以及所有更改检测操作,包括在视图上运行的ViewChildren,而不是 DOM。我已经测试了这段代码,我可以确认所有 DOM 侦听器和角度绑定都在工作。
2021-03-19 23:47:37
这是抛出错误,因为“parentElement.parentNode”为空
2021-03-20 23:47:37
此解决方案不起作用,抛出 null 并破坏 ng 结构
2021-03-25 23:47:37
这不会与 Angular 的变更检测和虚拟 DOM 混淆吗?
2021-03-28 23:47:37