Website-Suche

javascript - Angular2: Komponente ohne ihr umschließendes Tag rendern


Ich habe Schwierigkeiten, einen Weg zu finden, dies zu tun. In einer übergeordneten Komponente beschreibt die Vorlage eine table und ihr thead-Element, überträgt jedoch das Rendern des tbody an eine andere Komponente, so wie folgt:

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

Jede myResult-Komponente rendert ihr eigenes tr-Tag, im Grunde so:

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

Der Grund, warum ich dies nicht direkt in die übergeordnete Komponente einfüge (um die Notwendigkeit einer myResult-Komponente zu vermeiden), ist, dass die myResult-Komponente tatsächlich komplizierter ist als hier gezeigt, daher möchte ich ihr Verhalten in eine separate Komponente und Datei auslagern.

Das resultierende DOM sieht schlecht aus. Ich glaube, das liegt daran, dass es ungültig ist, da tbody nur tr-Elemente enthalten kann (siehe MDN), aber mein generiertes (vereinfachtes) DOM lautet:

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

Gibt es eine Möglichkeit, dasselbe mit einer Kindkomponente darzustellen, um das Rendern der Tabellenzeile zu kapseln, ohne das umschließende <my-result>-Tag?

Ich habe mir ng-content, DynamicComponentLoader, das ViewContainerRef angesehen, aber sie scheinen keine Lösung dafür zu bieten, soweit ich das sehen kann.

Antwort #1

Sie können Attributselektoren verwenden

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

und es dann wie benutzen

<td myTd></td>

Antwort #2

Sie benötigen "ViewContainerRef" und im my-result-Komponent machen Sie so etwas wie dies:

.html:

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

.ts:

@ViewChild('template', { static: true }) template;


constructor(
  private viewContainerRef: ViewContainerRef
) { }

ngOnInit() {
  this.viewContainerRef.createEmbeddedView(this.template);
}

Antwort #3

Sie können versuchen, das neue CSS display: contents zu verwenden

Hier ist mein Toolbar-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;
}

und das 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>

und in Gebrauch:

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

Antwort #4

Attributselektoren sind der beste Weg, dieses Problem zu lösen.

Also in deinem Fall:

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

meine-ergebnisse 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() {
  }

}

my-results html

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

mein-ergebnis 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() {
  }

}

mein-ergebnis html

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

Siehe funktionierenden StackBlitz: https://stackblitz.com/edit/angular-xbbegx

Antwort #5

Verwenden Sie diese Direktive an Ihrem Element

@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);
   }
}

Beispielnutzung:

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

Und im übergeordneten HTML rufst du wie üblich Card Element auf, zum Beispiel:

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

Die Ausgabe wird sein:

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

Verwandte Artikel