在Angular 2中如何检查<ng-content>是否为空?


91

假设我有一个组件:

@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

Answers:


96

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>`

23
有没有其他办法呢?与Aurelia后备广告时段相比,这很丑陋
宇航员

18
使用起来更安全ref.children.lengthtext如果您使用空格或换行符格式化html,则childNodes将包含节点,但子代仍为空。
国会

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
谢尔盖·迪乌巴

33

@pixelbits答案中有一些缺失。我们不仅需要检查children属性,因为父模板中的任何换行符或空格都会导致children元素的文本\换行符为空白。更好地检查.innerHTML.trim()它。

工作示例:

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

1
对我来说最好的答案:)
卡米尔·基列夫斯基(KamilKiełczewski)

我们可以使用此方法检查子元素是否为空,如果是则隐藏父元素吗?我尝试过,没有运气
Kavinda Jayakody

@KavindaJayakody是的,这将检查子元素是否为空。显示您的代码。
lfoma

* ngIf =“!ref.innerHTML.trim()”这部分将导致性能问题。修剪为O(n)。
RandomCode

1
@RandomCode是正确的,该函数将被多次调用。更好的方法是根据您的要求检查element.children.lengthelement.childnodes.length
Stefan Rein

31

编辑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
到目前为止,这是我用例的最佳解决方案。我不记得我们有一个emptyCSS伪类
julianobrasil

@Stefan默认内容仍然会呈现...只会被隐藏。因此,对于复杂的默认内容,这不是最佳解决方案。这是正确的吗?
BossOz

1
@BossOz你是对的。它将被渲染(如果您有角度组件,则将调用构造函数等)。此解决方案仅适用于愚蠢的视图。如果您有复杂的逻辑,涉及加载或类似的事情,那么我认为最好的办法是编写一个结构化指令,该指令可以获取模板/类(无论您要如何实现),并根据逻辑进行渲染。所需的组件或默认组件。注释部分对于示例来说太小了。我再评论一个应用程序中的另一个示例。
Stefan Rein

一种方法是让一个组件通过一个指令选择ContentChildren(使用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;

因此,在组件模板中,您可以检查content变量是否具有值

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

这是最干净,更好的答案。它开箱即用地支持ContentChild API。无法理解为什么最高答案得到这么多选票。
CarbonDry

10
OP询问ngContent是否为空-这意味着<MyContainer></MyContainer>。您的解决方案希望用户在MyContainer下创建一个子元素:<MyContainer><div #content></div></MyContainer>。虽然这是可能的,但我不会说这是优越的。
pixelbits '18 -10-3

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几乎没有什么共同之处,只是两者都是Web框架和名称。
君特Zöchbauer

2
甘特,你提出了两个非常好的观点。Angular不一定一定会因AngularJS的方式而受到污名化。但是,我要指出的是,第一点(以及我上面提到的那些观点)落在了工程学的哲学思想上。就是说,我已经成为软件耦合方面的专家,我建议不要(至少大量)使用@ViewChild。感谢您为我的评论增加一些平衡-我发现我们两个评论都与其他评论一样值得考虑。
科迪

2
@Cody也许您对此@ViewChild()有很强的意见来自这个名字。“子代”不一定是子代,它们只是组件的声明部分(如我所见)。如果为此标记创建的组件实例是访问,则当然是另外一个故事,因为它将至少需要有关其公共接口的知识,并且实际上会产生紧密的耦合。这是一个非常有趣的讨论。令我担心的是,我没有足够的时间来考虑这些问题,因为在这样的框架下,太多的时间被浪费在寻找任何方法上。
君特Zöchbauer

3
真正的反模式没有在API中公开:-(
Simon_Weaver

4

如果要显示默认内容,为什么不使用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'

请注意,transclude内容必须是一个HTML节点(不仅仅是文本)
M'sieur至pH”

1

就我而言,我必须隐藏空白ng-content的父级:

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

简单的CSS作品:

.wrapper {
  display: inline-block;

  &:empty {
    display: none;
  }
}

1

我已经通过使用@ContentChildren实现了一个解决方案装饰器某种程度上类似于的答案。

根据docs,此装饰器:

从内容DOM获取元素或指令的QueryList。每当添加,删除或移动子元素时,查询列表都将更新,并且查询列表的可观察到的更改将发出新值。

因此,父组件中的必要代码将是:

<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


1

在Angular 10中,它做了些微的更改。您将使用:

<div #ref><ng-content></ng-content></div> 
<span *ngIf="ref.children.length == 0">
  Display this if ng-content is empty!
</span>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.