Di Angular 2 bagaimana cara memeriksa apakah <ng-content> kosong?


92

Misalkan saya memiliki komponen:

@Component({
    selector: 'MyContainer',
    template: `
    <div class="container">
        <!-- some html skipped -->
        <ng-content></ng-content>
        <span *ngIf="????">Display this if ng-content is empty!</span>
        <!-- some html skipped -->
    </div>`
})
export class MyContainer {
}

Sekarang, saya ingin menampilkan beberapa konten default jika <ng-content>untuk komponen ini kosong. Adakah cara mudah untuk melakukan ini tanpa mengakses DOM secara langsung?


1
Kemungkinan duplikat dari Bagaimana memeriksa apakah ng-konten ada
titusfx

FYI, saya tahu jawaban yang diterima berfungsi, tapi saya pikir gaya yang lebih baik untuk meneruskan parameter input jenis "useDefault" ke komponen, default ke false.
bryan60

Jawaban:


96

Gabungkan ng-contentelemen HTML seperti a divuntuk mendapatkan referensi lokal, lalu ikat ngIfekspresi ke ref.children.length == 0:

template: `<div #ref><ng-content></ng-content></div> 
           <span *ngIf="ref.nativeElement.childNodes.length == 0">
              Display this if ng-content is empty!
           </span>`

24
Apakah tidak ada cara alternatif untuk ini? Karena ini jelek, dibandingkan dengan slot mundur Aurelia
Astronaut

18
Lebih aman digunakan ref.children.length. childNodes akan berisi textnode jika Anda memformat html Anda dengan spasi atau baris baru, tetapi anak akan tetap kosong.
Parlemen

5
Ada permintaan fitur untuk metode yang lebih baik pada pelacak masalah Angular: github.com/angular/angular/issues/12530 (mungkin ada baiknya menambahkan +1 di sana).
eppsilon

5
Saya akan memposting bahwa ini sepertinya tidak berfungsi sampai saya menyadari bahwa saya menggunakan contoh ref.childNodes.length == 0alih-alih ref.children.length == 0. Akan sangat membantu jika Anda dapat mengedit jawaban agar konsisten. Kesalahan mudah, bukan menghajar Anda. :)
Dustin Cleveland

3
Saya mengikuti contoh ini dan itu berfungsi dengan baik untuk saya. Tapi saya menggunakan !ref.hasChildNodes()alih-alihref.nativeElement.childNodes.length == 0
Sergey Dzyuba

35

Ada beberapa yang hilang dalam jawaban @pixelbits. Kita tidak hanya perlu memeriksa childrenproperti, karena setiap jeda baris atau spasi di template induk akan menyebabkan childrenelemen dengan teks kosong \ linebreaks. Lebih baik periksa .innerHTMLdan .trim()itu.

Contoh kerja:

<span #ref><ng-content></ng-content></span>
<span *ngIf="!ref.innerHTML.trim()">
    Content if empty
</span>

1
jawaban terbaik untuk saya :)
Kamil Kiełczewski

Bisakah kita menggunakan metode ini untuk memeriksa apakah elemen anak kosong dan menyembunyikan induk jika demikian? Saya mencoba, tidak berhasil
Kavinda Jayakody

@KavindaJayak Ya, ini akan memeriksa elemen anak untuk kosong. Tunjukkan kode Anda.
lfoma

* ngIf = "! ref.innerHTML.trim ()" Bagian ini akan menyebabkan masalah kinerja. Trim adalah O (n).
RandomCode

1
@RandomCode benar, fungsinya akan dipanggil beberapa kali. Cara yang lebih baik adalah dengan memeriksa element.children.length atau element.childnodes.length, tergantung pada apa yang ingin Anda minta.
Stefan Rein

33

EDIT 17.03.2020

CSS murni (2 solusi)

Menyediakan konten default jika tidak ada yang diproyeksikan ke ng-content.

Penyeleksi yang memungkinkan:

  1. :only-childpemilih. Lihat posting ini di sini :: Only-child Selector

    Yang ini membutuhkan lebih sedikit kode / markup. Dukungan sejak IE 9: Can I Use: only-child

  2. :emptypemilih. Baca lebih lanjut.

    Dukungan dari IE 9 dan sebagian sejak IE 7/8: https://caniuse.com/#feat=css-sel3

HTML

<div class="wrapper">
    <ng-content select="my-component"></ng-content>
</div>
<div class="default">
    This shows something default.
</div>

CSS

.wrapper:not(:empty) + .default {
    display: none;
}

Seandainya itu tidak berfungsi

Ketahuilah, bahwa memiliki setidaknya satu spasi kosong dianggap tidak kosong. Angular menghapus spasi, tetapi untuk berjaga-jaga jika tidak:

<div class="wrapper"><!--
    --><ng-content select="my-component"></ng-content><!--
--></div>

atau

<div class="wrapper"><ng-content select="my-component"></ng-content</div>

1
Sejauh ini, ini adalah solusi terbaik untuk kasus penggunaan saya. Saya tidak ingat kami memiliki emptypseudoclass css
julianobrasil

@ Stefan, konten default akan tetap dirender ... itu hanya akan disembunyikan. Jadi, untuk konten default yang kompleks, ini bukanlah solusi terbaik. Apakah ini benar?
BossOz

1
@BossO Anda benar. Ini akan diberikan (konstruktor, dll. Akan dipanggil jika Anda memiliki komponen sudut). Solusi ini hanya berfungsi baik untuk tampilan bodoh. Jika Anda memiliki logika yang kompleks, melibatkan pemuatan atau hal-hal seperti itu, taruhan terbaik Anda menurut saya adalah menulis arahan struktural, yang bisa mendapatkan template / kelas (bagaimanapun Anda ingin menerapkannya) dan tergantung pada logikanya, kemudian render komponen yang diinginkan atau yang default. Bagian komentar terlalu kecil untuk sebuah contoh. Saya berkomentar lagi untuk contoh lain yang kami miliki di salah satu aplikasi kami.
Stefan Rein

Salah satu caranya adalah memiliki komponen yang memilih ContentChildren melalui Directive (kueri dengan DirectiveClass, tetapi menggunakan sebagai arahan struktural, jadi tidak ada bootstrap awal yang terlibat hanya dengan markup): <loader [data]="allDataWeNeed"> <desired-component *loadingRender><desired-component> <other-default-component *renderWhenEmptyResult></other-default-component> </loader>dan dalam komponen pemuatan, Anda dapat menunjukkan pemuatan kinerja yang dirasakan, sementara data sedang dimuat. Anda dapat menulis / menyelesaikannya dengan berbagai cara. Ini adalah salah satu demonstrasi bagaimana Anda bisa menyelesaikannya.
Stefan Rein

21

Saat Anda memasukkan konten, tambahkan variabel referensi:

<div #content>Some Content</div>

dan di kelas komponen Anda dapatkan referensi ke konten yang diinjeksi dengan @ContentChild ()

@ContentChild('content') content: ElementRef;

jadi di template komponen Anda, Anda dapat memeriksa apakah variabel konten memiliki nilai

<div>
  <ng-content></ng-content>
  <span *ngIf="!content">
    Display this if ng-content is empty!
  </span>    
</div> 

Ini adalah jawaban yang paling bersih dan jauh lebih baik. Ini mendukung ContentChild API di luar kotak. Tidak mengerti mengapa jawaban teratas mendapat begitu banyak suara.
CarbonDry

10
OP menanyakan apakah ngContent kosong atau tidak - artinya <MyContainer></MyContainer>. Solusi Anda mengharapkan pengguna untuk membuat sub-elemen di bawah MyContainer: <MyContainer><div #content></div></MyContainer>. Meskipun ini adalah kemungkinan, saya tidak akan mengatakan ini lebih baik.
pixelbits

8

Suntikkan elementRef: ElementRefdan periksa apakah elementRef.nativeElementmemiliki anak. Ini mungkin hanya bekerja dengan encapsulation: ViewEncapsulation.Native.

Bungkus <ng-content>label dan periksa apakah ada anak-anak. Ini tidak berhasil encapsulation: ViewEncapsulation.Native.

<div #contentWrapper>
  <ng-content></ng-content>
</div>

dan periksa apakah memiliki anak

@ViewChild('contentWrapper') contentWrapper;

ngAfterViewInit() {
  contentWrapper.nativeElement.childNodes...
}

(tidak diuji)


3
Saya tidak menyukai ini karena penggunaan @ViewChild. Meskipun "Legal", ViewChild tidak boleh digunakan untuk mengakses node anak di dalam template. Ini adalah antipattern karena, sementara orang tua mungkin tahu tentang anak-anak, mereka tidak boleh berpasangan dengan mereka karena biasanya mengarah ke Pathological Coupling ; bentuk yang lebih tepat dari pengangkutan data atau penanganan permintaan adalah dengan menggunakan metodologi yang digerakkan oleh peristiwa .
Cody

5
@Cody terima kasih telah memposting komentar untuk downvote Anda. Sebenarnya saya tidak mengikuti argumentasi Anda. Template dan kelas komponen adalah satu unit - sebuah komponen. Saya tidak mengerti mengapa mengakses template dari kode harus antipattern. Angular (2/4) hampir tidak memiliki kesamaan dengan AngularJS kecuali keduanya adalah kerangka web dan namanya.
Günter Zöchbauer

2
Gunter, Anda membuat dua poin yang sangat bagus. Angular tidak harus selalu menetas dengan stigma dari cara AngularJS. Tapi saya akan menandai bahwa poin pertama (dan yang telah saya buat di atas) jatuh ke selokan filosofis teknik. Meskipun demikian, saya telah menjadi ahli dalam Software Coupling dan saya menyarankan agar tidak [setidaknya berat] menggunakan @ViewChild. Terima kasih telah menambahkan keseimbangan pada komentar saya - Saya menemukan kedua komentar kami sama layaknya untuk dipertimbangkan seperti yang lain.
Cody

2
@Cody mungkin pendapat kuat Anda tentang @ViewChild()berasal dari nama. "Anak-anak" belum tentu anak-anak, mereka hanyalah bagian deklaratif dari komponen (seperti yang saya lihat). Jika contoh komponen yang dibuat untuk markup ini adalah akses, ini tentu saja cerita yang berbeda, karena akan memerlukan pengetahuan tentang setidaknya antarmuka publik mereka dan ini akan benar-benar membuat hubungan yang erat. Ini adalah pembahasan yang sangat menarik. Saya khawatir bahwa saya tidak punya cukup waktu untuk memikirkan hal-hal seperti itu karena dengan kerangka seperti itu terlalu sering waktu habis untuk menemukan cara sama sekali.
Günter Zöchbauer

3
Tidak memaparkan ini di API adalah anti-pola yang sebenarnya :-(
Simon_Weaver

5

Jika Anda ingin menampilkan konten default mengapa Anda tidak menggunakan pemilih 'anak tunggal' dari css.

https://www.w3schools.com/cssref/sel_only-child.asp

misalnya: HTML

<div>
  <ng-content></ng-content>
  <div class="default-content">I am deafult</div>
</div>

css

.default-content:not(:only-child) {
   display: none;
}

Solusi yang cukup cerdas, jika Anda tidak ingin berurusan dengan ViewChild dan barang-barang di Komponen. Saya suka itu!
M'sieur Toph '

Harap perhatikan bahwa transclude konten harus berupa node HTML (bukan hanya teks)
M'sieur Toph '

2

Dengan Angular 10, ini sedikit berubah. Anda akan menggunakan:

<div #ref><ng-content></ng-content></div> 
<span *ngIf="ref.children.length == 0">
  Display this if ng-content is empty!
</span>

1

Dalam kasus saya, saya harus menyembunyikan induk dari ng-konten kosong:

<span class="ml-1 wrapper">
  <ng-content>
  </ng-content>
</span>

Pekerjaan css sederhana:

.wrapper {
  display: inline-block;

  &:empty {
    display: none;
  }
}

1

Saya telah menerapkan solusi dengan menggunakan dekorator @ContentChildren , yang entah bagaimana mirip dengan jawaban @ Lerner.

Menurut dokumen , dekorator ini:

Dapatkan QueryList elemen atau arahan dari DOM konten. Setiap kali elemen turunan ditambahkan, dihapus, atau dipindahkan, daftar kueri akan diperbarui, dan perubahan yang dapat diamati dari daftar kueri akan mengeluarkan nilai baru.

Jadi kode yang diperlukan dalam komponen induk adalah:

<app-my-component>
  <div #myComponentContent>
    This is my component content
  </div>
</app-my-component>

Di kelas komponen:

@ContentChildren('myComponentContent') content: QueryList<ElementRef>;

Kemudian, di template komponen:

<div class="container">
  <ng-content></ng-content>
  <span *ngIf="*ngIf="!content.length""><em>Display this if ng-content is empty!</em></span>
</div>

Contoh lengkap : https://stackblitz.com/edit/angular-jjjdqb

Saya telah menemukan solusi ini diimplementasikan dalam komponen sudut, untuk matSuffix, dalam komponen bidang formulir .

Dalam situasi ketika konten komponen dimasukkan nanti, setelah aplikasi diinisialisasi, kita juga dapat menggunakan implementasi reaktif, dengan berlangganan ke changesperistiwa QueryList:

export class MyComponentComponent implements AfterContentInit, OnDestroy {
  private _subscription: Subscription;
  public hasContent: boolean;

  @ContentChildren('myComponentContent') content: QueryList<ElementRef>;

  constructor() {}

  ngAfterContentInit(): void {
    this.hasContent = (this.content.length > 0);
    this._subscription = this.content.changes.subscribe(() => {
      // do something when content updates
      //
      this.hasContent = (this.content.length > 0);
    });
  }

  ngOnDestroy() {
    this._subscription.unsubscribe();
  }

}

Contoh lengkap : https://stackblitz.com/edit/angular-essvnq

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.