ใน Angular 2 จะตรวจสอบได้อย่างไรว่า <ng-content> ว่างหรือไม่


93

สมมติว่าฉันมีส่วนประกอบ:

@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 {
}

ตอนนี้ฉันต้องการแสดงเนื้อหาเริ่มต้นบางส่วนหาก<ng-content>สำหรับส่วนประกอบนี้ว่างเปล่า มีวิธีง่ายๆในการดำเนินการนี้โดยไม่ต้องเข้าถึง DOM โดยตรงหรือไม่?



FYI ฉันรู้ว่าคำตอบที่ยอมรับได้ผล แต่ฉันคิดว่ารูปแบบที่ดีกว่าที่จะส่งผ่านพารามิเตอร์อินพุตประเภท "useDefault" ไปยังคอมโพเนนต์โดยค่าเริ่มต้นเป็นเท็จ
bryan60

คำตอบ:


96

รวมng-contentองค์ประกอบ HTML เช่น a divเพื่อรับการอ้างอิงในเครื่องจากนั้นผูกngIfนิพจน์กับ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
ไม่มีทางเลือกอื่นสำหรับวิธีนี้หรือไม่? เพราะมันน่าเกลียดเมื่อเทียบกับช่องทางเลือกของ Aurelia
Astronaut

18
ref.children.lengthมันปลอดภัยในการใช้งาน childNodes จะมีtextโหนดหากคุณจัดรูปแบบ html ของคุณด้วยช่องว่างหรือบรรทัดใหม่ แต่ลูกจะยังคงว่างเปล่า
รัฐสภา

5
มีคำขอคุณลักษณะสำหรับวิธีการที่ดีกว่าในเครื่องมือติดตามปัญหาเชิงมุม: github.com/angular/angular/issues/12530 (อาจคุ้มค่าที่จะเพิ่ม +1 ที่นั่น)
eppsilon

5
ผมกำลังจะโพสต์ที่ว่านี้ไม่ได้ดูเหมือนจะทำงานจนกว่าฉันรู้ว่าฉันใช้ตัวอย่างของแทนref.childNodes.length == 0 ref.children.length == 0มันจะช่วยได้มากหากคุณสามารถแก้ไขคำตอบให้สอดคล้องกันได้ ผิดพลาดง่ายไม่ทุบตีคุณ :)
Dustin Cleveland

3
ฉันทำตามตัวอย่างนี้และมันก็ใช้ได้ดีสำหรับฉัน แต่ฉันใช้!ref.hasChildNodes()แทนref.nativeElement.childNodes.length == 0
Sergey Dzyuba

36

มีบางอย่างหายไปในคำตอบ @pixelbits เราจำเป็นต้องตรวจสอบchildrenคุณสมบัติไม่เพียงเท่านั้นเนื่องจากการแบ่งบรรทัดหรือการเว้นวรรคในเทมเพลตหลักจะทำให้childrenองค์ประกอบที่มีข้อความว่าง \ linebreaks ดีกว่าตรวจสอบ.innerHTMLและ.trim()มัน

ตัวอย่างการทำงาน:

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

1
คำตอบที่ดีที่สุดสำหรับฉัน :)
Kamil Kiełczewski

เราสามารถใช้วิธีนี้เพื่อตรวจสอบว่าองค์ประกอบลูกว่างเปล่าและซ่อนพาเรนต์ได้หรือไม่ ฉันพยายามแล้วไม่มีโชค
Kavinda Jayakody

@KavindaJayakody ใช่นี่จะตรวจสอบองค์ประกอบลูกว่าว่างเปล่า แสดงรหัสของคุณ
lfoma

* ngIf = "! ref.innerHTML.trim ()" ส่วนนี้จะทำให้เกิดปัญหาด้านประสิทธิภาพ Trim คือ O (n)
RandomCode

1
@RandomCode ถูกต้องฟังก์ชันจะถูกเรียกหลายครั้ง วิธีที่ดีกว่าคือตรวจสอบelement.children.length หรือelement.childnodes.lengthขึ้นอยู่กับสิ่งที่คุณต้องการขอ
Stefan Rein

33

แก้ไข 17.03.2020

CSS บริสุทธิ์ (2 โซลูชัน)

จัดเตรียมเนื้อหาดีฟอลต์หากไม่มีการฉายในเนื้อหา ng-content

ตัวเลือกที่เป็นไปได้:

  1. :only-childตัวเลือก ดูโพสต์นี้ที่นี่: ตัวเลือกลูกคนเดียว

    อันนี้ต้องการรหัส / มาร์กอัปน้อยกว่า รองรับตั้งแต่ IE 9: ฉันสามารถใช้ได้: ลูกคนเดียว

  2. :emptyตัวเลือก เพียงอ่านเพิ่มเติม

    รองรับจาก IE 9 และบางส่วนตั้งแต่ 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;
}

ในกรณีที่ไม่ทำงาน

โปรดทราบว่าการมีช่องว่างอย่างน้อยหนึ่งช่องถือว่าไม่ว่างเปล่า เชิงมุมลบช่องว่าง แต่ในกรณีที่ไม่ใช่:

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

หรือ

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

1
นี่เป็นทางออกที่ดีที่สุดสำหรับกรณีการใช้งานของฉัน ฉันจำไม่ได้ว่าเรามีemptypseudoclass css
julianobrasil

@ Stefan เนื้อหาเริ่มต้นจะแสดงผลต่อไป ... มันจะถูกซ่อนเท่านั้น ดังนั้นสำหรับเนื้อหาเริ่มต้นที่ซับซ้อนนี่ไม่ใช่ทางออกที่ดีที่สุด นี่ใช่มั้ย?
BossOz

1
@BossOz คุณพูดถูก จะแสดงผล (ตัวสร้าง ฯลฯ จะถูกเรียกถ้าคุณมีส่วนประกอบเชิงมุม) วิธีนี้ใช้ได้ผลดีกับมุมมองที่โง่เขลาเท่านั้น หากคุณมีตรรกะที่ซับซ้อนเกี่ยวข้องกับการโหลดหรือสิ่งต่างๆเช่นนั้นทางออกที่ดีที่สุดของคุณในความคิดของฉันคือการเขียนคำสั่งโครงสร้างซึ่งจะได้รับเทมเพลต / คลาส (อย่างไรก็ตามคุณต้องการนำไปใช้) และขึ้นอยู่กับตรรกะจากนั้นแสดงผล ส่วนประกอบที่ต้องการหรือค่าเริ่มต้น ส่วนความคิดเห็นมีขนาดเล็กเกินไปสำหรับตัวอย่าง ฉันแสดงความคิดเห็นอีกครั้งสำหรับตัวอย่างอื่นที่เรามีในหนึ่งในแอปของเรา
Stefan Rein

วิธีหนึ่งคือการมีส่วนประกอบที่เลือก ContentChildren ผ่านทาง Directive (สอบถามด้วย DirectiveClass แต่ใช้เป็นคำสั่งโครงสร้างดังนั้นจึงไม่มีการเริ่มต้นบูตที่เกี่ยวข้องกับการมีมาร์กอัป) <loader [data]="allDataWeNeed"> <desired-component *loadingRender><desired-component> <other-default-component *renderWhenEmptyResult></other-default-component> </loader>และในองค์ประกอบการโหลดคุณสามารถแสดงการรับรู้การโหลดขณะที่ กำลังโหลดข้อมูล คุณสามารถเขียน / แก้ปัญหาได้หลายวิธี นี่เป็นการสาธิตวิธีแก้ปัญหา
Stefan Rein

21

เมื่อคุณฉีดเนื้อหาให้เพิ่มตัวแปรอ้างอิง:

<div #content>Some Content</div>

และในคลาสคอมโพเนนต์ของคุณได้รับการอ้างอิงถึงเนื้อหาที่แทรกด้วย @ContentChild ()

@ContentChild('content') content: ElementRef;

ดังนั้นในเทมเพลตคอมโพเนนต์ของคุณคุณสามารถตรวจสอบได้ว่าตัวแปรเนื้อหามีค่าหรือไม่

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

นี่คือคำตอบที่สะอาดที่สุดและดีกว่ามาก สนับสนุน ContentChild API นอกกรอบ ไม่เข้าใจว่าทำไมคำตอบยอดนิยมถึงได้รับการโหวตมากมาย
CarbonDry

10
สหกรณ์ถามว่าจริงหรือไม่ ngContent เป็นที่ว่างเปล่า - <MyContainer></MyContainer>ที่นี้หมายถึง วิธีการแก้ปัญหาของคุณคาดว่าผู้ใช้สามารถสร้างองค์ประกอบย่อยภายใต้ <MyContainer><div #content></div></MyContainer>MyContainer: แม้ว่านี่จะเป็นไปได้ แต่ฉันจะไม่บอกว่านี่เป็นสิ่งที่ดีกว่า
pixelbits

8

ฉีดยาelementRef: ElementRefและตรวจดูว่าelementRef.nativeElementมีบุตรหรือไม่ สิ่งนี้อาจใช้ได้กับencapsulation: ViewEncapsulation.Nativeไฟล์.

ห่อ<ng-content>แท็กและตรวจสอบว่ามีลูกหรือไม่ encapsulation: ViewEncapsulation.Nativeนี้ไม่ได้ทำงานกับ

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

และตรวจสอบว่ามีลูกหรือไม่

@ViewChild('contentWrapper') contentWrapper;

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

(ไม่ผ่านการทดสอบ)


3
ฉันลดลงเนื่องจากการใช้@ViewChild. ในขณะที่ "กฎหมาย" ไม่ควรใช้ ViewChild เพื่อเข้าถึงโหนดลูกภายในเทมเพลต นี้เป็นเพราะ antipattern ในขณะที่พ่อแม่อาจรู้เกี่ยวกับเด็กที่พวกเขาไม่ควรคู่กับพวกเขาก็มักจะนำไปสู่ทางพยาธิวิทยา Coupling ; รูปแบบที่เหมาะสมมากขึ้นของข้อมูลการขนส่งหรือการร้องขอในการจัดการคือการใช้วิธีการเหตุการณ์ที่ขับเคลื่อนด้วย
Cody

5
@ ขอบคุณมากสำหรับการโพสต์ความคิดเห็นในการโหวตของคุณ อันที่จริงฉันไม่ทำตามคำโต้แย้งของคุณ เทมเพลตและคลาสส่วนประกอบเป็นหน่วยเดียว - ส่วนประกอบ ฉันไม่เห็นว่าเหตุใดการเข้าถึงเทมเพลตจากโค้ดจึงควรเป็นแบบปฏิปักษ์ Angular (2/4) แทบไม่มีอะไรที่เหมือนกันกับ AngularJS ยกเว้นว่าทั้งสองเป็นเฟรมเวิร์กและชื่อ
GünterZöchbauer

2
กุนเทอร์คุณทำสองคะแนนได้ดีจริงๆ Angular ไม่ควรฟักออกมาด้วยความอัปยศจากวิธีการของ AngularJS แต่ฉันจะทำเครื่องหมายว่าจุดแรก (และที่ฉันได้ทำไว้ข้างต้น) ตกอยู่ในร่องลึกทางปรัชญาของวิศวกรรม ที่กล่าวว่าฉันได้กลายเป็นสิ่งที่มีความเชี่ยวชาญในซอฟแวร์การเชื่อมต่อและผมแนะนำให้กับการใช้ [ที่หนักน้อย] @ViewChildของ ขอขอบคุณที่เพิ่มความสมดุลให้กับความคิดเห็นของฉัน - ฉันพบว่าความคิดเห็นของเราทั้งสองนั้นควรค่าแก่การพิจารณาเช่นเดียวกับความคิดเห็นอื่น ๆ
Cody

2
@Cody บางทีความคิดเห็นที่แข็งแกร่งของคุณเกี่ยวกับ@ViewChild()มาจากชื่อ "เด็ก" ไม่จำเป็นต้องเป็นเด็ก แต่เป็นเพียงส่วนที่เปิดเผยขององค์ประกอบ (อย่างที่ฉันเห็น) หากอินสแตนซ์ของคอมโพเนนต์ที่สร้างขึ้นสำหรับมาร์กอัปนี้เป็นการเข้าถึงสิ่งนี้แน่นอนว่าเป็นเรื่องราวที่แตกต่างกันเนื่องจากต้องมีความรู้เกี่ยวกับอินเทอร์เฟซสาธารณะเป็นอย่างน้อยและสิ่งนี้จะสร้างการมีเพศสัมพันธ์ที่แน่นหนา นี่เป็นการอภิปรายที่น่าสนใจมาก มันทำให้ฉันกังวลว่าฉันไม่มีเวลามากพอที่จะคิดเกี่ยวกับเรื่องดังกล่าวเพราะด้วยกรอบเช่นนี้บ่อยครั้งที่เวลาถูกเผาผลาญจนหาหนทางใด ๆ เลย
GünterZöchbauer

3
การไม่เปิดเผยสิ่งนี้ใน API เป็นการต่อต้านรูปแบบที่แท้จริง :-(
Simon_Weaver

5

หากคุณต้องการแสดงเนื้อหาเริ่มต้นทำไมคุณไม่เพียงแค่ใช้ตัวเลือก "ลูกคนเดียว" จาก css

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

เช่น: HTML

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

css

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

ค่อนข้างเป็นโซลูชันที่ชาญฉลาดหากคุณไม่ต้องการจัดการกับ ViewChild และสิ่งของต่างๆใน Component ฉันชอบมัน!
M'sieur Toph '

โปรดสังเกตว่าการแปลงเนื้อหาต้องเป็นโหนด HTML (ไม่ใช่เฉพาะข้อความ)
M'sieur Toph '

2

ด้วย Angular 10 มีการเปลี่ยนแปลงเล็กน้อย คุณจะใช้:

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

1

ในกรณีของฉันฉันต้องซ่อนพาเรนต์ของเนื้อหา ng ที่ว่างเปล่า:

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

งาน css อย่างง่าย:

.wrapper {
  display: inline-block;

  &:empty {
    display: none;
  }
}

1

ฉันได้ใช้วิธีแก้ปัญหาโดยใช้มัณฑนากร@ContentChildrenซึ่งคล้ายกับคำตอบของ @ Lerner

ตามเอกสารมัณฑนากรนี้:

รับ QueryList ขององค์ประกอบหรือคำสั่งจาก DOM ของเนื้อหา ทุกครั้งที่มีการเพิ่มลบหรือย้ายองค์ประกอบลูกรายการแบบสอบถามจะได้รับการอัปเดตและการเปลี่ยนแปลงที่สังเกตได้ของรายการคำค้นหาจะแสดงค่าใหม่

ดังนั้นรหัสที่จำเป็นในองค์ประกอบหลักจะเป็น:

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

ในคลาสส่วนประกอบ:

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

จากนั้นในเทมเพลตส่วนประกอบ:

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

ตัวอย่างเต็ม : https://stackblitz.com/edit/angular-jjjdqb

ฉันพบว่าโซลูชันนี้ถูกนำไปใช้ในส่วนประกอบเชิงมุมสำหรับmatSuffixในส่วนประกอบของฟิลด์ฟอร์ม

ในสถานการณ์ที่เนื้อหาของคอมโพเนนต์ถูกแทรกในภายหลังหลังจากเริ่มต้นแอปแล้วเรายังสามารถใช้การใช้งานแบบรีแอคทีฟโดยสมัครรับchangesเหตุการณ์ของ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();
  }

}

ตัวอย่างเต็ม : https://stackblitz.com/edit/angular-essvnq

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