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


100

我正在努力寻找一种方法来做到这一点。在父组件中,模板描述了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>

每个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-contentDynamicComponentLoaderViewContainerRef,但他们似乎并不至于我可以看到提供一个解决的办法。


你能举个可行的例子吗?
zakaria amine

2
正确的答案就在这里,还有一个更简单的示例stackoverflow.com/questions/46671235/…–
sancelot

1
建议的答案均无效或不完整。正确的答案在这里描述了plunker样品stackoverflow.com/questions/46671235/...
sancelot

Answers:


95

您可以使用属性选择器

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

然后像

<td myTd></td>

您组件的选择器是否包含[]?您是否将组件添加到declarations: [...]模块中?如果组件已在其他模块中注册,您是否将此其他模块添加到imports: [...]要使用该组件的模块中?
君特Zöchbauer

1
setAttribute,不是我的代码。但是我已经弄清楚了,我需要在模板中使用实际的顶层标签作为组件的标签,而不是ng-container使用新的工作方式<ul smMenu class="nav navbar-nav" [submenus]="root?.Submenus" [title]="root?.Title"></ul>
Aviad P.17.8.25

1
您无法在ng-container上设置属性组件,因为它已从DOM中删除。因此,您需要在实际的HTML标签上进行设置。
Robouste

2
@AviadP。非常感谢...我知道需要进行一些小的更改,对此我已经失去了理智。因此,代替此:<ul class="nav navbar-nav"> <li-navigation [navItems]="menuItems"></li-navigation> </ul>我使用了此方法(将li导航更改为属性选择器之后)<ul class="nav navbar-nav" li-navigation [navItems]="menuItems"></ul>
Mahesh

3
如果您尝试增加材料成分,则此答案不起作用,例如<mat-toolbar my-toolbar-rows>会抱怨您只能使用一个组件选择器而不是多个。我可以去<my-toolbar-component>,然后将mat-toolbar放入div中
罗伯特·金(Robert King)

20

您需要“ 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);
  }

3
奇迹般有效!谢谢。我在Angular 8中使用了以下内容:@ViewChild('template', {static: true}) template;
AlainD。

1
这工作了。我遇到了一种情况,我正在使用css display:grid,但仍然不能将<my-result>标签作为父元素中的第一个元素,而my-result的所有子元素都作为my-result的同级元素。问题在于,一个额外的幽​​灵元素仍然打破了网格7列“ grid-template-columns:repeat(7,1fr);”。它渲染了8个元素。1个用于我的结果的幽灵占位符和7个列标题。解决方法是通过在标签中放入<my-result style =“ display:none”>来简单地隐藏它。此后,css网格系统就像一个魅力一样工作。
RandallTo

该解决方案甚至在my-result需要能够创建新my-result同级的更复杂的情况下也可以使用。因此,假设您有一个层次结构,my-result其中每行可以包含“子”行。在这种情况下,使用属性选择器将不起作用,因为第一个选择器进入tbody,但是第二个选择器不能进入内部tbodytr
丹尼尔·

1
使用此方法时,如何避免出现ExpressionChangedAfterItHaHasBeenCheckedError?
gyozo kudor

@gyozokudor可能使用setTimeout
Diego Fernando Murillo Valenci

12

在您的元素上使用此指令

@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>

6
由于'parentElement.parentNode'为null
引发

这个主意很好,但是不能正常工作:-(
jpmottin

9

属性选择器是解决此问题的最佳方法。

因此,在您的情况下:

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

我的结果

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>

我的结果

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>

查看可工作的stackblitz:https ://stackblitz.com/edit/angular-xbbegx


选择器:“ my-results,[my-results]”,那么我可以将my-results用作HTML中标记的属性。这是正确的答案。它适用于Angular 8.2。
Don Dilanga

8

您可以尝试使用新的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>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.