Angular2: rende un componente senza il suo tag di wrapping


100

Sto lottando per trovare un modo per farlo. In un componente padre, il modello descrive a tablee il suo theadelemento, ma delega il rendering di tbodya un altro componente, in questo modo:

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

Ogni componente myResult rende il proprio trtag, sostanzialmente in questo modo:

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

Il motivo per cui non lo metto direttamente nel componente genitore (evitando la necessità di un componente myResult) è che il componente myResult è in realtà più complicato di quanto mostrato qui, quindi voglio mettere il suo comportamento in un componente e in un file separati.

Il DOM risultante sembra cattivo. Credo che questo sia perché non è valido, in quanto tbodypuò contenere solo trelementi (vedi MDN) , ma il mio DOM generato (semplificato) è:

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

C'è un modo in cui possiamo ottenere la stessa cosa renderizzata, ma senza il <my-result>tag di wrapping , e utilizzando ancora un componente per essere l'unico responsabile del rendering di una riga di tabella?

Ho guardato ng-content, DynamicComponentLoaderla ViewContainerRef, ma non sembrano fornire una soluzione a questo, per quanto posso vedere.


puoi mostrare un esempio funzionante?
zakaria amine

2
La risposta giusta è lì, con un campione plunker stackoverflow.com/questions/46671235/...
sancelot

1
Nessuna delle risposte proposte funziona o è completa. La risposta giusta è descritto qui con un campione plunker stackoverflow.com/questions/46671235/...
sancelot

Risposte:


95

È possibile utilizzare selettori di attributi

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

e poi usalo come

<td myTd></td>

Il selettore del tuo componente contiene il []? Hai aggiunto il componente a declarations: [...]un modulo? Se il componente è registrato in un modulo diverso, hai aggiunto questo altro modulo a imports: [...]del modulo in cui desideri utilizzare il componente?
Günter Zöchbauer

1
No, setAttributenon è il mio codice. Ma ho capito, avevo bisogno di utilizzare il tag di primo livello effettivo nel mio modello come tag per il mio componente invece del ng-containernuovo utilizzo lavorativo è<ul smMenu class="nav navbar-nav" [submenus]="root?.Submenus" [title]="root?.Title"></ul>
Aviad P.

1
Non puoi impostare il componente dell'attributo su ng-container perché è stato rimosso dal DOM. Ecco perché è necessario impostarlo su un tag HTML effettivo.
Robouste

2
@AviadP. Grazie mille ... sapevo che era necessario un piccolo cambiamento e stavo impazzendo per questo. Quindi, invece di questo: <ul class="nav navbar-nav"> <li-navigation [navItems]="menuItems"></li-navigation> </ul>ho usato questo (dopo aver cambiato la navigazione in un selettore di attributi)<ul class="nav navbar-nav" li-navigation [navItems]="menuItems"></ul>
Mahesh

3
questa risposta non funziona se stai cercando di aumentare un componente materiale, ad esempio <mat-toolbar my-toolbar-rows> si lamenterà del fatto che puoi avere solo un selettore di componenti non multiplo. Potrei andare <my-toolbar-component> ma poi sta inserendo la mat-toolbar all'interno di un div
robert king

20

Hai bisogno di "ViewContainerRef" e all'interno del componente my-result fai qualcosa del genere:

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
Funziona come un fascino! Grazie. Ho usato invece in Angular 8 quanto segue:@ViewChild('template', {static: true}) template;
AlainD.

1
Questo ha funzionato. Ho avuto una situazione in cui stavo usando css display: grid e non puoi avere ancora il tag <my-result> come primo elemento nel genitore con tutti gli elementi figlio del mio risultato che si rompono come fratelli al mio risultato . Il problema era che un elemento fantasma in più rompeva ancora le griglie 7 colonne "grid-template-colonne: repeat (7, 1fr);" e stava rendendo 8 elementi. 1 segnaposto fantasma per il mio risultato e le 7 intestazioni di colonna. Il modo per aggirare questo problema è stato semplicemente nasconderlo inserendo <my-result style = "display: none"> nel tag. Il sistema a griglia CSS ha funzionato alla grande dopo.
Randall,

Questa soluzione funziona anche in un caso più complesso, in cui è my-resultnecessario essere in grado di creare nuovi my-resultfratelli. Quindi, immagina di avere una gerarchia in my-resultcui ogni riga può avere righe "secondarie". In tal caso, l'utilizzo di un selettore di attributi non funzionerebbe, poiché il primo selettore va in tbody, ma il secondo non può andare in un interno tbodyné in un tr.
Daniel

1
Quando lo uso come posso evitare di ottenere ExpressionChangedAfterItHasBeenCheckedError?
gyozo kudor

@gyozokudor forse usando un setTimeout
Diego Fernando Murillo Valenci il

12

Usa questa direttiva sul tuo elemento

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

Utilizzo di esempio:

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

e nell'html genitore chiami elemento carta come al solito, ad esempio:

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

L'output sarà:

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

6
Questo genera un errore perché "parentElement.parentNode" è nullo
emirhosseini

L'idea è buona ma non funziona correttamente :-(
jpmottin

9

I selettori di attributi sono il modo migliore per risolvere questo problema.

Quindi nel tuo caso:

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

i miei risultati 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() {
  }

}

i miei risultati html

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

mio-risultato 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() {
  }

}

il mio risultato html

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

Vedi stackblitz funzionante: https://stackblitz.com/edit/angular-xbbegx


selettore: 'my-results, [my-results]', quindi posso utilizzare i miei-risultati come attributo del tag in HTML. Questa è la risposta corretta. Funziona in Angular 8.2.
Don Dilanga

8

puoi provare a usare il nuovo css display: contents

ecco il mio scss della barra degli strumenti:

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

e l'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>

e in uso:

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

3

Un'altra opzione al giorno d'oggi è il ContribNgHostModulereso disponibile dal @angular-contrib/commonpacchetto .

Dopo aver importato il modulo puoi aggiungerlo host: { ngNoHost: '' }al tuo @Componentdecoratore e nessun elemento di wrapping verrà renderizzato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.