Trong Angular 2 làm thế nào để kiểm tra xem <ng-content> có trống không?


92

Giả sử tôi có một thành phần:

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

Bây giờ, tôi muốn hiển thị một số nội dung mặc định nếu <ng-content>thành phần này trống. Có cách nào dễ dàng để thực hiện việc này mà không cần truy cập trực tiếp vào DOM không?



FYI, tôi biết câu trả lời được chấp nhận hoạt động, nhưng tôi nghĩ tốt hơn là chuyển tham số đầu vào kiểu "useDefault" cho các thành phần, được đặt mặc định là false.
bryan60,

Câu trả lời:


97

Bao bọc ng-contentmột phần tử HTML như a divđể nhận tham chiếu cục bộ đến nó, sau đó liên kết ngIfbiểu thức với 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>`

23
Không có cách nào thay thế cho điều này? Bởi vì điều này là xấu, so với các vị trí dự phòng Aurelia
Phi hành gia

18
Nó an toàn hơn để sử dụng ref.children.length. childNodes sẽ chứa textcác nút nếu bạn định dạng html của mình bằng dấu cách hoặc dòng mới, nhưng các nút con vẫn trống.
quốc hội

5
Có một yêu cầu tính năng cho một phương pháp tốt hơn trên trình theo dõi vấn đề Angular: github.com/angular/angular/issues/12530 (có thể đáng để thêm +1 ở đó).
eppsilon

5
Tôi đã định đăng rằng điều này dường như không hoạt động cho đến khi tôi nhận ra rằng tôi đã sử dụng ví dụ của ref.childNodes.length == 0thay vì ref.children.length == 0. Sẽ rất hữu ích nếu bạn có thể chỉnh sửa câu trả lời cho phù hợp. Sai lầm dễ dàng, không bashing bạn. :)
Dustin Cleveland

3
Tôi đã làm theo ví dụ này và nó hoạt động tốt cho tôi. Nhưng tôi đã sử dụng !ref.hasChildNodes()thay vìref.nativeElement.childNodes.length == 0
Sergey Dzyuba

34

Có một số bị thiếu trong câu trả lời @pixelbits. Chúng tôi không chỉ cần kiểm tra thuộc childrentính, vì bất kỳ ngắt dòng hoặc khoảng trắng nào trong mẫu mẹ sẽ gây ra childrenphần tử có dấu ngắt dòng văn bản trống. Tốt hơn để kiểm tra .innerHTML.trim()nó.

Ví dụ làm việc:

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

1
Câu trả lời tốt nhất cho tôi :)
Kamil Kiełczewski

Chúng ta có thể sử dụng phương pháp này để kiểm tra xem phần tử con có trống không và ẩn phần tử cha nếu có? Tôi cố gắng, không có may mắn
Kavinda Jayakody

@KavindaJayakody Có, điều này sẽ kiểm tra phần tử con để trống. Hiển thị mã của bạn.
lfoma

* ngIf = "! ref.innerHTML.trim ()" Phần này sẽ gây ra các vấn đề về hiệu suất. Trim là O (n).
RandomCode

1
@RandomCode là đúng, hàm sẽ được gọi nhiều lần. Một cách tốt hơn là kiểm tra element.children.length hoặc element.childnodes.length, tùy thuộc vào những gì bạn muốn yêu cầu.
Stefan Rein

31

CHỈNH SỬA 17.03.2020

CSS thuần túy (2 giải pháp)

Cung cấp nội dung mặc định nếu không có gì được chiếu vào ng-content.

Các bộ chọn có thể có:

  1. :only-childbộ chọn. Xem bài đăng này tại đây :: only-child Selector

    Cái này yêu cầu ít mã / đánh dấu hơn. Hỗ trợ kể từ IE 9: Tôi có thể sử dụng: only-child

  2. :emptybộ chọn. Chỉ cần đọc thêm.

    Hỗ trợ từ IE 9 và một phần kể từ 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;
}

Trong trường hợp nó không hoạt động

Hãy lưu ý rằng có ít nhất một khoảng trắng được coi là không trống. Angular loại bỏ khoảng trắng, nhưng chỉ trong trường hợp không có:

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

hoặc là

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

1
Cho đến nay đây là giải pháp tốt nhất cho trường hợp sử dụng của tôi. Tôi không nhớ chúng tôi đã có một tân cổ điển emptycss
julianobrasil

@Stefan, nội dung mặc định sẽ được hiển thị bằng mọi cách ... nó sẽ chỉ ẩn. Vì vậy, đối với nội dung mặc định phức tạp, đây không phải là giải pháp tốt nhất. Thê nay đung không?
BossOz

1
@BossOz Bạn nói đúng. Nó sẽ được kết xuất (hàm tạo, v.v. sẽ được gọi nếu bạn có một thành phần góc). Giải pháp này chỉ hoạt động tốt cho các chế độ xem câm. Nếu bạn có logic phức tạp, liên quan đến tải hoặc những thứ tương tự, cách tốt nhất của bạn theo ý kiến ​​của tôi là viết một chỉ thị cấu trúc, có thể lấy một mẫu / lớp (tuy nhiên bạn muốn triển khai nó) và tùy thuộc vào logic, sau đó hiển thị thành phần mong muốn hoặc thành phần mặc định. Phần bình luận quá nhỏ để làm ví dụ. Tôi bình luận một lần nữa cho một ví dụ khác mà chúng tôi có trong một trong các ứng dụng của mình.
Stefan Rein

Có một cách là có một thành phần chọn ContentChildren thông qua một chỉ thị (truy vấn với DrivingClass, nhưng sử dụng làm chỉ thị cấu trúc, vì vậy không liên quan đến khởi động ban đầu mà chỉ cần có đánh dấu): <loader [data]="allDataWeNeed"> <desired-component *loadingRender><desired-component> <other-default-component *renderWhenEmptyResult></other-default-component> </loader>và trong thành phần tải, bạn có thể hiển thị tải hiệu suất nhận biết, dữ liệu đang tải. Bạn có thể viết / giải nó theo nhiều cách khác nhau. Đây là một minh chứng về cách bạn có thể giải quyết nó.
Stefan Rein

21

Khi bạn chèn nội dung, hãy thêm một biến tham chiếu:

<div #content>Some Content</div>

và trong lớp thành phần của bạn nhận được một tham chiếu đến nội dung được đưa vào với @ContentChild ()

@ContentChild('content') content: ElementRef;

vì vậy trong mẫu thành phần của bạn, bạn có thể kiểm tra xem biến nội dung có giá trị

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

Đây là câu trả lời sạch sẽ nhất và tốt hơn nhiều. Nó hỗ trợ API ContentChild ngay lập tức. Không thể hiểu tại sao câu trả lời hàng đầu lại nhận được nhiều phiếu bầu như vậy.
CarbonDry

10
OP yêu cầu xem có trống hay không ngContent - điều này có nghĩa là <MyContainer></MyContainer>. Giải pháp của bạn hy vọng người dùng tạo ra một yếu tố phụ thuộc MyContainer: <MyContainer><div #content></div></MyContainer>. Mặc dù đây là một khả năng, tôi sẽ không nói rằng điều này là vượt trội.
pixelbits

8

Tiêm elementRef: ElementRefvà kiểm tra xem elementRef.nativeElementcó con nào không. Điều này có thể chỉ hoạt động với encapsulation: ViewEncapsulation.Native.

Quấn <ng-content>thẻ và kiểm tra xem thẻ có con không. Điều này không hoạt động với encapsulation: ViewEncapsulation.Native.

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

và kiểm tra xem nó có con nào không

@ViewChild('contentWrapper') contentWrapper;

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

(không thử nghiệm)


3
Tôi đã từ chối cái này vì việc sử dụng @ViewChild. Trong khi "Hợp pháp", ViewChild không nên được sử dụng để truy cập các nút con trong một mẫu. Đây là một phản vật chất bởi vì, trong khi cha mẹ có thể biết về con cái, họ không nên kết đôi với chúng vì nó thường dẫn đến Ghép đôi bệnh lý ; một hình thức vận chuyển dữ liệu hoặc xử lý yêu cầu thích hợp hơn là sử dụng các phương pháp luận hướng sự kiện .
Cody

5
@Cody cảm ơn vì đã đăng bình luận cho phản hồi của bạn. Thực ra tôi không theo lập luận của bạn. Khuôn mẫu và lớp thành phần là một đơn vị - một thành phần. Tôi không hiểu tại sao việc truy cập mẫu từ mã phải là phản vật chất. Angular (2/4) hầu như không có điểm chung nào với AngularJS ngoại trừ việc cả hai đều là web framework và tên gọi.
Günter Zöchbauer

2
Gunter, bạn có hai điểm thực sự tốt. Angular không nhất thiết phải nở ra với một sự kỳ thị theo cách của AngularJS. Nhưng tôi đánh dấu rằng điểm đầu tiên (và những điểm tôi đã nói ở trên) rơi vào rãnh triết học của kỹ thuật. Điều đó nói rằng, tôi đã trở thành một chuyên gia về Khớp nối phần mềm và tôi khuyên không nên sử dụng [ít nhất là nặng] @ViewChild. Cảm ơn vì đã thêm một số cân bằng cho nhận xét của tôi - tôi thấy cả hai nhận xét của chúng tôi cũng đáng được xem xét như những nhận xét khác.
Cody

2
@Cody có lẽ ý kiến ​​mạnh mẽ của bạn về @ViewChild()cái tên. "Children" không nhất thiết phải là con, chúng chỉ là phần khai báo của thành phần (như tôi thấy). Nếu các cá thể thành phần được tạo cho đánh dấu này là quyền truy cập, thì điều này dĩ nhiên là một câu chuyện khác, bởi vì nó sẽ yêu cầu kiến ​​thức về ít nhất là giao diện công khai của chúng và điều này thực sự sẽ tạo ra sự liên kết chặt chẽ. Đây là một cuộc thảo luận rất thú vị. Tôi lo lắng rằng tôi không có đủ thời gian để suy nghĩ về những vấn đề như vậy bởi vì với những khuôn khổ như vậy, thời gian thường bị đốt cháy để tìm ra bất kỳ cách nào.
Günter Zöchbauer,

3
Không để lộ này trong API là chống mẫu thực :-(
Simon_Weaver

4

Nếu bạn muốn hiển thị nội dung mặc định, tại sao bạn không sử dụng bộ chọn 'only-child' từ css.

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

ví dụ: HTML

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

css

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

Đúng hơn là giải pháp thông minh, nếu bạn không muốn đối phó với ViewChild và các nội dung trong Thành phần. Tôi thích nó!
M'sieur Toph '

Vui lòng lưu ý rằng nội dung transclude phải là một nút HTML (không chỉ văn bản)
M'sieur Toph '

1

Trong trường hợp của tôi, tôi phải ẩn phụ huynh của ng-content trống:

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

Css đơn giản hoạt động:

.wrapper {
  display: inline-block;

  &:empty {
    display: none;
  }
}

1

Tôi đã triển khai một giải pháp bằng cách sử dụng @ContentChildren decorator, bằng cách nào đó tương tự như câu trả lời của @ Lerner.

Theo tài liệu , người trang trí này:

Nhận QueryList của các phần tử hoặc chỉ thị từ DOM nội dung. Bất kỳ khi nào một phần tử con được thêm, xóa hoặc di chuyển, danh sách truy vấn sẽ được cập nhật và những thay đổi có thể quan sát được của danh sách truy vấn sẽ tạo ra một giá trị mới.

Vì vậy, mã cần thiết trong thành phần mẹ sẽ là:

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

Trong lớp thành phần:

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

Sau đó, trong mẫu thành phần:

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

Ví dụ đầy đủ : https://stackblitz.com/edit/angular-jjjdqb

Tôi đã tìm thấy giải pháp này được triển khai trong các thành phần góc cạnh matSuffix, trong trường biểu mẫu thành phần .

Trong trường hợp nội dung của thành phần được đưa vào sau này, sau khi ứng dụng được khởi chạy, chúng tôi cũng có thể sử dụng triển khai phản ứng, bằng cách đăng ký changessự kiện của 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();
  }

}

Ví dụ đầy đủ : https://stackblitz.com/edit/angular-essvnq


1

Với Angular 10, nó đã thay đổi một chút. Bạn sẽ sử dụng:

<div #ref><ng-content></ng-content></div> 
<span *ngIf="ref.children.length == 0">
  Display this if ng-content is empty!
</span>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.