В Angular 2 как проверить, пуст ли <ng-content>?


92

Допустим, у меня есть компонент:

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



К вашему сведению, я знаю, что принятый ответ работает, но я думаю, что лучше передать входной параметр типа «useDefault» компонентам, значение по умолчанию - false.
bryan60

Ответы:


97

Оберните 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
Существует запрос функции для лучшего метода отслеживания проблем 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
Сергей Дзюба

34

В ответе @pixelbits некоторые отсутствуют. Нам нужно проверять не только childrenсвойство, потому что любые разрывы строк или пробелы в родительском шаблоне вызовут childrenэлемент с пустым текстом \ разрывы строк . Лучше проверить .innerHTMLи.trim() это.

Рабочий пример:

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

1
лучший ответ для меня :)
Камил Келчевски

Можем ли мы использовать этот метод, чтобы проверить, пуст ли дочерний элемент, и скрыть родительский, если да? Я пробовал, но
безуспешно

@KavindaJayakody Да, это проверит дочерний элемент на предмет пустоты. Покажи свой код.
lfoma

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

1
@RandomCode правильный, функция будет вызываться несколько раз. Лучше проверить element.children.length или element.childnodes.length, в зависимости от того, что вы хотите спросить.
Стефан Рейн

32

РЕДАКТИРОВАТЬ 17.03.2020

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

Предоставляет контент по умолчанию, если в ng-content ничего не проецируется.

Возможные селекторы:

  1. :only-childселектор. См. Этот пост здесь :: only-child Selector

    Это требует меньше кода / разметки. Поддержка с 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 с помощью директивы (запрос с помощью DirectiveClass, но использование в качестве структурной директивы, поэтому начальная <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> 

Это самый чистый и лучший ответ. Он поддерживает ContentChild API из коробки. Не могу понять, почему за главный ответ так много голосов.
CarbonDry 02

10
OP спросил, пуст ли ngContent - это означает <MyContainer></MyContainer>. Ваше решение ожидает пользователям создавать подэлемент под MyContainer: <MyContainer><div #content></div></MyContainer>. Хотя это возможно, я бы не сказал, что это лучше.
pixelbits 03

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 не следует использовать для доступа к дочерним узлам в шаблоне. Это антипаттерн, потому что, хотя родители могут знать о детях, они не должны объединяться с ними, поскольку это обычно приводит к патологическому сцеплению ; более подходящей формой передачи данных или обработки запросов является использование методологий, управляемых событиями .
Коди

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

2
Гюнтер, ты сделал два действительно хороших замечания. Angular не обязательно должен оклеветать методы AngularJS. Но я бы отметил, что первый пункт (и те, что я сделал выше) падает в философскую канаву инженерии. Тем не менее, я стал в некотором роде экспертом в области Software Coupling, и я бы не советовал [по крайней мере, интенсивно] использовать @ViewChild. Спасибо за добавление баланса в мои комментарии - я считаю оба наших комментария столь же достойными внимания, как и другой.
Cody

2
@Cody, возможно, твое твердое мнение @ViewChild()исходит из названия. «Дочерние элементы» не обязательно являются дочерними, это просто декларативная часть компонента (как я это вижу). Если экземпляры компонентов, созданные для этой разметки, являются доступами, это, конечно, другая история, потому что для этого потребуются знания, по крайней мере, об их общедоступном интерфейсе, и это фактически создаст тесную связь. Это очень интересное обсуждение. Меня беспокоит, что у меня нет достаточно времени, чтобы подумать о таких вещах, потому что с такими фреймворками слишком часто время тратится, чтобы найти какой-либо способ вообще.
Günter Zöchbauer

3
Не раскрывать это в API - настоящий
антипаттерн

5

Если вы хотите отображать контент по умолчанию, почему бы вам просто не использовать селектор 'only-child' из 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-content:

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

Простой css работает:

.wrapper {
  display: inline-block;

  &:empty {
    display: none;
  }
}

1

Я реализовал решение, используя @ContentChildren декоратор , который чем-то похож на ответ .

Согласно документам , этот декоратор:

Получите 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.