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

Gre*_*reg 75 javascript angular

我正在努力找到一种方法来做到这一点.在父组件中,模板描述了a table及其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>
Run Code Online (Sandbox Code Playgroud)

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

<tr>
  <td>{{ entry.name }}</td>
  <td>{{ entry.time }}</td>
</tr>
Run Code Online (Sandbox Code Playgroud)

我没有直接将它放在父组件中(避免需要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>
Run Code Online (Sandbox Code Playgroud)

有没有什么方法我们可以得到相同的渲染,但没有包装<my-result>标签,并仍然使用组件独自负责渲染表行?

我已经看过了ng-content,DynamicComponentLoaderViewContainerRef,但他们似乎并不至于我可以看到提供一个解决的办法.

Gün*_*uer 70

您可以使用属性选择器

@Component({
  selector: '[myTd]'
  ...
})
Run Code Online (Sandbox Code Playgroud)

然后像使用它一样

<td myTd></td>
Run Code Online (Sandbox Code Playgroud)

  • 没有 `setAttribute` 不是我的代码。但我已经弄清楚了,我需要使用模板中的实际顶级标签作为我的组件的标签,而不是“ng-container”,所以新的工作用法是“&lt;ul smMenu class =“nav navbar-nav” [submenus]="root?.Submenus" [title]="root?.Title"&gt;&lt;/ul&gt;` (2认同)
  • 您无法在 ng-container 上设置属性组件,因为它已从 DOM 中删除。这就是为什么您需要在实际的 HTML 标记上设置它。 (2认同)
  • @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>` (2认同)
  • 如果您尝试增加材料成分,则此答案不起作用,例如&lt;mat-toolbar my-toolbar-rows&gt;会抱怨您只能使用一个组件选择器而不是多个。我可以去&lt;my-toolbar-component&gt;,然后将mat-toolbar放入div中 (2认同)
  • 该解决方案适用于简单的情况(如OP),但如果您需要创建一个“my-results”,其中可以生成更多“my-results”,那么您需要 [`ViewContainerRef`](https://angular.io /api/core/ViewContainerRef)(检查下面[@Slim的答案](/sf/answers/3982134131/))。该解决方案在这种情况下不起作用的原因是第一个属性选择器位于“tbody”中,但是内部选择器在哪里?它不能位于“tr”中,也不能将“tbody”放在另一个“tbody”中。 (2认同)

rob*_*ing 23

你可以尝试使用新的 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;
}
Run Code Online (Sandbox Code Playgroud)

和 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>
Run Code Online (Sandbox Code Playgroud)

并在使用中:

<navigation-toolbar (toggle)="snav.toggle()"></navigation-toolbar>
Run Code Online (Sandbox Code Playgroud)

  • 哈哈 IE11 说不! (2认同)
  • *显示:内容*可以完成这项工作。这就是我一直在寻找的,它允许子组件采用其样式,而不会被额外的子组件的名称标签(选择器)攻击 (2认同)
  • 我认为值得注意的是,使用“display:contents”可能会导致一些可访问性问题,因此应该谨慎使用(至少直到它在所有主要浏览器中完全安全之前):https://caniuse.com/css-显示内容 (2认同)

Nic*_*ray 13

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

所以在你的情况下:

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Time</th>
    </tr>
  </thead>
  <tbody my-results>
  </tbody>
</table>
Run Code Online (Sandbox Code Playgroud)

我的结果 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() {
  }

}
Run Code Online (Sandbox Code Playgroud)

我的结果 html

  <tr my-result [entry]="entry" *ngFor="let entry of entries"><tr>
Run Code Online (Sandbox Code Playgroud)

我的结果 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() {
  }

}
Run Code Online (Sandbox Code Playgroud)

我的结果 html

  <td>{{ entry.name }}</td>
  <td>{{ entry.time }}</td>
Run Code Online (Sandbox Code Playgroud)

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


小智 11

在您的元素上使用此指令

@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);
   }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

<div class="card" remove-wrapper>
   This is my card component
</div>
Run Code Online (Sandbox Code Playgroud)

并在父html中照常调用card元素,例如:

<div class="cards-container">
   <card></card>
</div>
Run Code Online (Sandbox Code Playgroud)

输出将是:

<div class="cards-container">
   <div class="card" remove-wrapper>
      This is my card component
   </div>
</div>
Run Code Online (Sandbox Code Playgroud)

  • 这会引发错误,因为'parentElement.parentNode'为null (4认同)
  • 这不会扰乱 Angular 的变更检测和虚拟 DOM 吗? (4认同)

小智 8

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

的HTML:

<ng-template #template>
    <tr>
       <td>Lisa</td>
       <td>333</td>
    </tr>
 </ng-template>
Run Code Online (Sandbox Code Playgroud)

ts:

@ViewChild('template') template;


  constructor(
    private viewContainerRef: ViewContainerRef
  ) { }

  ngOnInit() {
    this.viewContainerRef.createEmbeddedView(this.template);
  }
Run Code Online (Sandbox Code Playgroud)

  • 奇迹般有效!谢谢。我在 Angular 8 中使用了以下内容:`@ViewChild('template', {static: true}) template;` (8认同)
  • 谢谢!这是唯一对我有用的方法。除了您的代码之外,我还在最后执行 `this.viewContainerRef.element.nativeElement.remove()` ,以删除原始(空)组件。 (4认同)
  • 这有效。我遇到了一种情况,我正在使用 css display:grid 并且您不能让它仍然将 &lt;my-result&gt; 标记作为父级中的第一个元素,并将 my-result 的所有子元素呈现为 my-result 的兄弟元素。问题是,一个额外的幽​​灵元素仍然破坏了网格 7 列“grid-template-columns:repeat(7, 1fr);” 它正在渲染 8 个元素。1 个幽灵占位符用于显示我的结果和 7 个列标题。解决此问题的方法是通过将 &lt;my-result style="display:none"&gt; 放入标记中来简单地隐藏它。CSS 网格系统在那之后就像一个魅力。 (3认同)
  • 如果有人对 this.viewContainerRef 有错误,请将代码从 ngOnInit 移至 ngAfterViewInit。 (3认同)

mjs*_*sen 7

现在的另一种选择是ContribNgHostModule@angular-contrib/common包中提供

导入模块后,您可以添加host: { ngNoHost: '' }@Component装饰器中,并且不会呈现任何包装元素。

  • 只是发表评论,因为看起来这个包不再维护,并且 Angular 9+ 存在错误 (3认同)