Angular2: แสดงผลองค์ประกอบโดยไม่มีแท็กตัด


110

ฉันกำลังดิ้นรนเพื่อหาวิธีทำสิ่งนี้ ในองค์ประกอบหลักแม่แบบจะอธิบายถึง 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>

ส่วนประกอบ 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-content, DynamicComponentLoaderที่ViewContainerRef, แต่พวกเขาไม่ได้ดูเหมือนจะให้วิธีแก้ปัญหานี้เท่าที่ผมสามารถมองเห็น


คุณช่วยแสดงตัวอย่างการทำงานได้ไหม
zakaria amine

3
คำตอบที่ถูกต้องมีตัวอย่าง plunker stackoverflow.com/questions/46671235/…
sancelot

2
คำตอบที่เสนอไม่ได้ผลหรือสมบูรณ์ คำตอบที่ถูกต้องอธิบายไว้ที่นี่ด้วยตัวอย่าง plunker stackoverflow.com/questions/46671235/…
sancelot

คำตอบ:


104

คุณสามารถใช้ตัวเลือกแอตทริบิวต์

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

แล้วใช้มันเช่น

<td myTd></td>

ตัวเลือกส่วนประกอบของคุณมี[]หรือไม่? คุณได้เพิ่มส่วนประกอบลงdeclarations: [...]ในโมดูลหรือไม่? หากคอมโพเนนต์ถูกลงทะเบียนในโมดูลอื่นคุณได้เพิ่มโมดูลอื่นนี้ลงimports: [...]ในโมดูลที่คุณต้องการใช้คอมโพเนนต์หรือไม่
GünterZöchbauer

1
ไม่ใช่setAttributeไม่ใช่รหัสของฉัน แต่ฉันคิดออกแล้วฉันจำเป็นต้องใช้แท็กระดับบนสุดจริงในเทมเพลตของฉันเป็นแท็กสำหรับส่วนประกอบของฉันแทนที่จะng-containerใช้การใช้งานแบบใหม่<ul smMenu class="nav navbar-nav" [submenus]="root?.Submenus" [title]="root?.Title"></ul>
Aviad P.

1
คุณไม่สามารถตั้งค่าคอมโพเนนต์แอตทริบิวต์บน ng-container ได้เนื่องจากถูกลบออกจาก DOM นั่นเป็นเหตุผลที่คุณต้องตั้งค่าบนแท็ก HTML จริง
Robouste

2
@Aviad ป. ขอบคุณมาก ... ฉันรู้ว่ามีการเปลี่ยนแปลงเล็กน้อยที่จำเป็นและฉันก็หมดความคิดกับเรื่องนี้ แทนสิ่งนี้: <ul class="nav navbar-nav"> <li-navigation [navItems]="menuItems"></li-navigation> </ul>ฉันใช้สิ่งนี้ (หลังจากเปลี่ยน li-navigation เป็นตัวเลือกแอตทริบิวต์)<ul class="nav navbar-nav" li-navigation [navItems]="menuItems"></ul>
Mahesh

4
คำตอบนี้ใช้ไม่ได้หากคุณพยายามเพิ่มส่วนประกอบของวัสดุเช่น <mat-toolbar my-toolbar-row> จะบ่นว่าคุณสามารถมีตัวเลือกส่วนประกอบได้เพียงตัวเดียวเท่านั้น ฉันสามารถไปที่ <my-toolbar-component> ได้ แต่แล้วมันก็วาง mat-toolbar ไว้ใน div
robert king

26

คุณต้องมี "ViewContainerRef" และภายในองค์ประกอบผลลัพธ์ของฉันทำสิ่งนี้:

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

6
ใช้งานได้เหมือนมีเสน่ห์! ขอขอบคุณ. ฉันใช้ใน Angular 8 สิ่งต่อไปนี้แทน:@ViewChild('template', {static: true}) template;
AlainD.

2
สิ่งนี้ได้ผล ฉันมีสถานการณ์ที่ฉันใช้ css display: grid และคุณไม่สามารถมีได้ แต่ยังมีแท็ก <my-result> เป็นองค์ประกอบแรกในพาเรนต์ที่มีองค์ประกอบลูกทั้งหมดของการแสดงผลของฉันเป็นพี่น้องกับผลลัพธ์ของฉัน . ปัญหาคือองค์ประกอบโกสต์ที่เพิ่มขึ้นหนึ่งรายการยังคงทำลายกริด 7 คอลัมน์ "grid-template-column: repeat (7, 1fr);" และมันแสดงผล 8 องค์ประกอบ 1 ตัวยึดตำแหน่งโกสต์สำหรับ my-result และ 7 คอลัมน์ส่วนหัว วิธีแก้ปัญหานี้คือเพียงแค่ซ่อนโดยใส่ <my-result style = "display: none"> ในแท็กของคุณ ระบบกริด css ทำงานได้อย่างมีเสน่ห์หลังจากนั้น
RandallTo

โซลูชันนี้ใช้งานได้แม้ในกรณีที่ซับซ้อนกว่าซึ่งmy-resultต้องสามารถสร้างmy-resultพี่น้องใหม่ได้ ลองนึกภาพคุณมีลำดับชั้นmy-resultที่แต่ละแถวสามารถมีแถว "ย่อย" ได้ ในกรณีนั้นการใช้ตัวเลือกแอตทริบิวต์จะไม่ทำงานเนื่องจากตัวเลือกแรกจะอยู่ในตัวเลือก แต่ตัวเลือกtbodyที่สองไม่สามารถอยู่ในภายในtbodyหรือในไฟล์tr.
Daniel

1
เมื่อฉันใช้สิ่งนี้ฉันจะหลีกเลี่ยงการ ExpressionChangedAfterItHasBeenCheckedError ได้อย่างไร
gyozo kudor

@gyozokudor อาจจะใช้ setTimeout
Diego Fernando Murillo Valenci

16

คุณสามารถลองใช้ 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>

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 หลักคุณเรียกองค์ประกอบการ์ดตามปกติตัวอย่างเช่น:

<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' เป็นโมฆะ
emirhosseini

1
ไอเดียดี แต่ทำงานไม่ถูกต้อง :-(
jpmottin

10

ตัวเลือกแอตทริบิวต์เป็นวิธีที่ดีที่สุดในการแก้ปัญหานี้

ดังนั้นในกรณีของคุณ:

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

ผลลัพธ์ของฉัน 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 ผลลัพธ์ของฉัน

  <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


selector: 'my-results, [my-results]' จากนั้นฉันสามารถใช้ my-results เป็นแอตทริบิวต์ของแท็กใน HTML นี่คือคำตอบที่ถูกต้อง มันทำงานใน Angular 8.2
Don Dilanga

5

อีกตัวเลือกหนึ่งในปัจจุบันคือContribNgHostModuleทำใช้ได้จากแพคเกจ@angular-contrib/common

หลังจากนำเข้าโมดูลคุณสามารถเพิ่มhost: { ngNoHost: '' }ไปยัง@Componentมัณฑนากรของคุณได้และจะไม่มีการแสดงองค์ประกอบการตัด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.