Angular2: merender komponen tanpa tag pembungkusnya

100

Saya berjuang untuk menemukan cara untuk melakukan ini. Dalam komponen induk, template mendeskripsikan a tabledan theadelemennya, tetapi mendelegasikan rendering tbodyke komponen lain, seperti ini:

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

Setiap komponen myResult membuat trtagnya sendiri , pada dasarnya seperti ini:

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

Alasan saya tidak meletakkan ini secara langsung di komponen induk (menghindari kebutuhan akan komponen myResult) adalah karena komponen myResult sebenarnya lebih rumit daripada yang ditampilkan di sini, jadi saya ingin meletakkan perilakunya dalam komponen dan file terpisah.

DOM yang dihasilkan terlihat buruk. Saya yakin ini karena tidak valid, karena tbodyhanya dapat berisi trelemen (lihat MDN) , tetapi DOM yang saya buat (disederhanakan) adalah:

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

Apakah ada cara kita bisa mendapatkan hal yang sama diberikan, tetapi tanpa pembungkus <my-result> tag , dan saat masih menggunakan komponen untuk menjadi satu-satunya yang bertanggung jawab untuk merender baris tabel?

Saya telah melihat ng-content, DynamicComponentLoader, yang ViewContainerRef, tetapi mereka tampaknya tidak memberikan solusi untuk ini sejauh yang saya bisa melihat.

Greg
sumber
bisakah Anda menunjukkan contoh yang berfungsi?
zakaria amine
2
Jawaban yang benar ada di sana, dengan sampel plunker stackoverflow.com/questions/46671235/…
sancelot
1
Tak satu pun dari jawaban yang diusulkan berfungsi, atau lengkap. Jawaban yang benar dijelaskan di sini dengan contoh plunker stackoverflow.com/questions/46671235/…
sancelot

Jawaban:

97

Anda dapat menggunakan pemilih atribut

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

dan kemudian gunakan seperti itu

<td myTd></td>
Günter Zöchbauer
sumber
Apakah pemilih komponen Anda berisi []? Apakah Anda menambahkan komponen ke declarations: [...]modul? Jika komponen terdaftar di modul yang berbeda, apakah Anda menambahkan modul lain ini ke imports: [...]modul tempat Anda ingin menggunakan komponen tersebut?
Günter Zöchbauer
1
Tidak, setAttributebukan kode saya. Tapi saya sudah menemukannya, saya perlu menggunakan tag tingkat atas yang sebenarnya di template saya sebagai tag untuk komponen saya alih-alih ng-containerjadi penggunaan baru yang berfungsi<ul smMenu class="nav navbar-nav" [submenus]="root?.Submenus" [title]="root?.Title"></ul>
Aviad P.
1
Anda tidak dapat menyetel komponen atribut pada ng-container karena sudah dihapus dari DOM. Itulah mengapa Anda perlu mengaturnya pada tag HTML yang sebenarnya.
Robouste
2
@Wisatakeluarga. Terima kasih banyak ... Saya tahu ada perubahan kecil yang diperlukan dan saya kehilangan akal sehat atas hal ini. Jadi alih-alih ini: <ul class="nav navbar-nav"> <li-navigation [navItems]="menuItems"></li-navigation> </ul>Saya menggunakan ini (setelah mengubah navigasi-li ke pemilih atribut)<ul class="nav navbar-nav" li-navigation [navItems]="menuItems"></ul>
Mahesh
3
jawaban ini tidak berfungsi jika Anda mencoba menambah komponen material misalnya <mat-toolbar my-toolbar-row> akan mengeluh bahwa Anda hanya dapat memiliki satu pemilih komponen, bukan beberapa. Saya bisa pergi <my-toolbar-component> tapi kemudian meletakkan mat-toolbar di dalam div
robert king
22

Anda memerlukan "ViewContainerRef" dan di dalam komponen hasil-saya melakukan sesuatu seperti ini:

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);
  }
Ramping
sumber
4
Bekerja seperti pesona! Terima kasih. Saya menggunakan Angular 8 sebagai gantinya:@ViewChild('template', {static: true}) template;
AlainD.
2
Ini berhasil. Saya mengalami situasi ketika saya menggunakan css display: grid dan Anda tidak dapat memilikinya masih memiliki tag <my-result> sebagai elemen pertama dalam induk dengan semua elemen anak hasil saya yang dirender sebagai saudara ke hasil-saya . Masalahnya adalah bahwa satu elemen hantu tambahan masih merusak grid 7 kolom "grid-template-kolom: ulangi (7, 1fr);" dan itu merender 8 elemen. 1 placeholder hantu untuk hasil-saya dan 7 judul kolom. Solusi untuk ini adalah dengan menyembunyikannya dengan menempatkan <my-result style = "display: none"> di tag Anda. Sistem grid css bekerja seperti pesona setelah itu.
Randall Hingga
Solusi ini berfungsi bahkan dalam kasus yang lebih kompleks, di mana my-resultharus dapat membuat my-resultsaudara baru . Jadi, bayangkan Anda memiliki hierarki di my-resultmana setiap baris dapat memiliki baris "turunan". Dalam hal ini, menggunakan selektor atribut tidak akan berfungsi, karena selektor pertama masuk ke tbody, tetapi selektor kedua tidak bisa masuk internal tbodyatau a tr.
Daniel
1
Ketika saya menggunakan ini, bagaimana saya bisa menghindari ExpressionChangedAfterItHasBeenCheckedError?
gyozo kudor
@gyozokudor mungkin menggunakan setTimeout
Diego Fernando Murillo Valenci
12

Gunakan arahan ini di elemen Anda

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

Contoh penggunaan:

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

dan di html induk Anda memanggil elemen kartu seperti biasa, misalnya:

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

Outputnya adalah:

<div class="cards-container">
   <div class="card" remove-wrapper>
      This is my card component
   </div>
</div>
Shlomi Aharoni
sumber
6
Ini membuat kesalahan karena 'parentElement.parentNode' adalah null
emirhosseini
1
Idenya bagus tapi tidak berfungsi dengan baik :-(
jpmottin
9

Pemilih atribut adalah cara terbaik untuk mengatasi masalah ini.

Jadi dalam kasus Anda:

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

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

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

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

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

Lihat stackblitz yang berfungsi: https://stackblitz.com/edit/angular-xbbegx

Nicholas Murray
sumber
selector: 'my-results, [my-results]', maka saya dapat menggunakan hasil-saya sebagai atribut tag di HTML. Ini jawaban yang benar. Ia bekerja di Angular 8.2.
Don Dilanga
9

Anda dapat mencoba menggunakan css baru display: contents

inilah scss toolbar saya:

: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;
}

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

dan sedang digunakan:

<navigation-toolbar (toggle)="snav.toggle()"></navigation-toolbar>
robert king
sumber
3

Pilihan lain saat ini adalah yang ContribNgHostModuledisediakan dari @angular-contrib/commonpaket .

Setelah mengimpor modul, Anda dapat menambahkan host: { ngNoHost: '' }ke @Componentdekorator Anda dan tidak ada elemen pembungkus yang akan dirender.

mjswensen.dll
sumber