У 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" компонентам, за замовчуванням значення false.
bryan60

Відповіді:


96

Wrap ng-contentв HTML - елемент як у 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
Астронавт

18
Це безпечніше у використанні ref.children.length. childNodes буде містити textвузли, якщо ви форматуєте свій HTML з пробілами або новими рядками, але діти все одно будуть порожніми.
парламент

5
Існує запит на функцію щодо кращого методу в трекуванні видань Angular: github.com/angular/angular/issues/12530 (можливо, варто додати туди +1).
eppsilon

5
Я збирався опублікувати, що це, здається, не працює, поки не зрозумів, що використовував приклад ref.childNodes.length == 0замість ref.children.length == 0. Це допомогло б групі, якби ви змогли відредагувати відповідь, щоб вона була послідовною. Легка помилка, не бити вас. :)
Дастін Клівленд

3
Я наслідував цей приклад, і він у мене чудово працює. Але я використав !ref.hasChildNodes()замістьref.nativeElement.childNodes.length == 0
Сергій Дзюба

36

У відповіді @pixelbits відсутні деякі. Нам потрібно перевірити не тільки childrenвластивість, оскільки будь-які розриви рядків або пробіли в батьківському шаблоні спричинять childrenелемент із порожнім текстом \ linebreaks. Краще перевірити .innerHTMLі .trim()це.

Робочий приклад:

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

1
найкраща відповідь для мене :)
Каміль Kiełczewski

Чи можемо ми використати цей метод, щоб перевірити, чи порожній елемент порожній, і приховати батьківський, якщо так? Я намагався, не пощастило
Кавінда Джаякоді

@KavindaJayakody Так, це перевірить дочірній елемент на порожній. Покажіть свій код.
lfoma

* ngIf = "! ref.innerHTML.trim ()" Ця частина спричинить проблеми з продуктивністю. Обрізка - O (n).
RandomCode

1
@RandomCode правильний, функція буде викликатися кілька разів. Кращий спосіб - перевірити element.children.length або element.childnodes.length, залежно від того, про що ви хочете попросити.
Стефан Рейн,

33

РЕДАКТУВАТИ 17.03.2020

Чистий CSS (2 рішення)

Надає вміст за замовчуванням, якщо в ng-вміст нічого не проектується.

Можливі селектори:

  1. :only-childселектор. Дивіться цю публікацію тут:: Селектор лише для дітей

    Для цього потрібно менше коду / розмітки. Підтримка з IE 9: Чи можу я використовувати: only-child

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

Якщо це не працює

Майте на увазі, що наявність принаймні одного пробілу вважається непустотою. Angular видаляє пробіли, але про всяк випадок, якщо це не так:

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

або

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

1
Безумовно, це було найкраще рішення для мого випадку використання. Я не згадував, що у нас був emptyпсевдоклас css
julianobrasil

@Stefan вміст за замовчуванням все одно буде відтворений ... це буде лише приховати. Отже, для складного контенту за замовчуванням це не найкраще рішення. Чи це правильно?
BossOz

1
@BossOz Ви маєте рацію. Він буде відображений (конструктор тощо буде викликаний, якщо у вас кутовий компонент). Це рішення працює лише для німих поглядів. Якщо у вас складна логіка, яка включає завантаження чи подібні речі, на мій погляд, найкращим варіантом є написати структурну директиву, яка може отримати шаблон / клас (однак ви хочете це реалізувати), а залежно від логіки, тоді потрібний компонент або компонент за замовчуванням. Розділ коментарів занадто малий для прикладу. Я ще раз коментую ще один приклад, який ми маємо в одному з наших додатків.
Стефан Рейн

Одним із способів було б мати компонент, який вибирає ContentChildren за допомогою директиви (запит за допомогою ДирективиКлас, але використання як структурна директива, тому жодне початкове завантаження не пов’язане лише з розміткою): <loader [data]="allDataWeNeed"> <desired-component *loadingRender><desired-component> <other-default-component *renderWhenEmptyResult></other-default-component> </loader>а в компоненті завантаження ви можете показати сприймане завантаження продуктивності, тоді як дані завантажуються. Ви можете написати / вирішити це різними способами. Це одна демонстрація того, як ви можете це вирішити.
Стефан Рейн,

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> 

Це найчистіша і набагато краща відповідь. Він підтримує API ContentChild нестандартно. Не можу зрозуміти, чому головна відповідь набрала стільки голосів.
CarbonDry

10
OP запитав, чи порожній ngContent - це означає <MyContainer></MyContainer>. Ваше рішення очікує користувачам створювати піделементи під MyContainer: <MyContainer><div #content></div></MyContainer>. Хоча це можливість, я б не сказав, що це вища річ.
пікселі

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. Поки "Legal", ViewChild не повинен використовуватися для доступу до дочірніх вузлів у шаблоні. Це є антипаттерном, оскільки, хоча батьки можуть знати про дітей, вони не повинні поєднуватися з ними, оскільки це зазвичай призводить до патологічного зчеплення ; більш прийнятною формою передачі даних або обробки запитів є використання методологій, керованих подіями .
Коді,

5
@Cody дякую за те, що ви опублікували коментар до вашого голосу проти. Насправді я не дотримуюся вашої аргументації. Шаблон і клас компонентів є єдиною одиницею - компонентом. Я не розумію, чому доступ до шаблону з коду повинен бути антипаттерном. Angular (2/4) майже не має нічого спільного з AngularJS, за винятком того, що обидва - це веб-фреймворки та назва.
Günter Zöchbauer

2
Гантер, ти робиш два справді хороші моменти. Angular не повинен обов’язково висиджуватися з клеймом із шляхів AngularJS. Але я б зазначив, що перший пункт (і ті, про які я говорив вище) потрапляє до філософського жолоба техніки. Тим не менш, я став дещо експертом із зчеплення програмного забезпечення, і я б радив не використовувати [принаймні важке] використання @ViewChild. Дякуємо, що додали баланс до моїх коментарів - я вважаю, що обидва наші коментарі так само гідні розгляду, як і інші.
Коді,

2
@Cody, можливо, ваша тверда думка про це @ViewChild()походить від назви. "Діти" - це не обов'язково діти, вони є лише декларативною частиною компонента (як я бачу). Якщо екземпляри компонентів, створені для цієї розмітки, мають доступ, це, звичайно, інша історія, оскільки для цього потрібні знання принаймні про їх загальнодоступний інтерфейс, і це насправді створило б щільне зв’язування. Це дуже цікава дискусія. Мене турбує те, що у мене не вистачає часу на обдумування таких питань, тому що з такими рамками занадто часто спалюється час, щоб взагалі знайти якийсь шлях.
Günter Zö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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.