Angular2: визуализировать компонент без его тега обертывания

Я изо всех сил пытаюсь найти способ сделать это. В родительском компоненте шаблон описывает элемент 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>

Есть ли способ получить то же самое, что и рендеринг, но без тэга wrapping <my-result>, и все еще используя компонент, который должен отвечать за визуализацию строки таблицы?

Я просмотрел ng-content, DynamicComponentLoader, ViewContainerRef, но они, похоже, не дают решения для этого, насколько я могу видеть.

Ответ 1

Вы можете использовать селектор атрибутов

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

а затем используйте его как

<td myTd></td>

Ответ 2

Используйте эту директиву на вашем элементе

@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 вы вызываете элемент карты как обычно, например:

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

Выход будет:

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

Ответ 3

Вам нужно "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);
  }

Ответ 5

Селекторы атрибутов - лучший способ решить эту проблему.

Итак, в вашем случае:

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

my-results 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 {

  const entries = [
    { name: 'Entry One', time: '10:00'},
    { name: 'Entry Two', time: '10:05 '},
    { name: 'Entry Three', time: '10:10'},
  ];

  constructor() { }

  ngOnInit() {
  }

}

html my-results

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

my-result 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 my-result

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

Смотрите рабочий стек: https://stackblitz.com/edit/angular-xbbegx

Ответ 6

Помимо выбранного ответа выше, вы также можете использовать ng-template или ng-container