如何检查 <ng-content> 是不是为空? (到目前为止在 Angular 2+ 中)

Posted

技术标签:

【中文标题】如何检查 <ng-content> 是不是为空? (到目前为止在 Angular 2+ 中)【英文标题】:How to check whether <ng-content> is empty? (in Angular 2+ till now)如何检查 <ng-content> 是否为空? (到目前为止在 Angular 2+ 中) 【发布时间】:2016-05-08 12:39:44 【问题描述】:

假设我有一个组件:

@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 

现在,如果此组件的&lt;ng-content&gt; 为空,我想显示一些默认内容。有没有一种简单的方法可以在不直接访问 DOM 的情况下做到这一点?

【问题讨论】:

How to check whether ng-content exists的可能重复 仅供参考,我知道接受的答案有效,但我认为将“useDefault”类型的输入参数传递给组件会更好,默认为 false。 【参考方案1】:

ng-content 包裹在像div 这样的HTML 元素中以获得对其的本地引用,然后将ngIf 表达式绑定到ref.children.length == 0

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

已针对 Angular 12 更新; 旧逻辑 ("ref.nativeElement.childNodes.length") 会出错,因为如今 nativeElementundefined

【讨论】:

没有别的办法吗?因为这很难看,与 Aurelia 后备插槽相比 使用ref.children.length 更安全。如果您使用空格或换行符格式化 html,childNodes 将包含 text 节点,但子节点仍将为空。 在 Angular 问题跟踪器上有一个更好的方法的功能请求:github.com/angular/angular/issues/12530(可能值得在那里添加 +1)。 我正要发布这似乎不起作用,直到我意识到我使用了ref.childNodes.length == 0 的示例而不是ref.children.length == 0。如果您可以编辑答案以保持一致,那将有很大帮助。简单的错误,不是抨击你。 :) 我遵循了这个例子,它对我来说很好用。但是我用!ref.hasChildNodes()而不是ref.nativeElement.childNodes.length == 0【参考方案2】:

编辑 17.03.2020

纯 CSS(2 个解决方案)

如果没有投射到 ng-content 中,则提供默认内容。

可能的选择器:

    :only-child 选择器。在此处查看此帖子::only-child Selector

    这个需要更少的代码/标记。从 IE 9 开始支持:Can I Use :only-child

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

【讨论】:

到目前为止,这是我用例的最佳解决方案。我不记得我们有一个 empty css 伪类 @Stefan 无论如何都会呈现默认内容......它只会隐藏。因此,对于复杂的默认内容,这不是最佳解决方案。对吗? @BossOz 你是对的。它将被渲染(如果您有角度组件,将调用构造函数等)。此解决方案仅适用于哑视图。如果你有复杂的逻辑,涉及加载或类似的东西,我认为你最好的选择是编写一个结构指令,它可以获得一个模板/类(但是你想实现它)并根据逻辑,然后渲染所需的组件或默认组件。评论部分对于示例来说太小了。我再次评论我们在其中一个应用中的另一个示例。 一种方法是让组件通过指令选择 ContentChildren(使用 DirectiveClass 进行查询,但用作结构指令,因此仅使用标记就不会涉及初始引导):&lt;loader [data]="allDataWeNeed"&gt; &lt;desired-component *loadingRender&gt;&lt;desired-component&gt; &lt;other-default-component *renderWhenEmptyResult&gt;&lt;/other-default-component&gt; &lt;/loader&gt; 和加载组件,您可以在数据加载时显示感知的性能加载。您可以用不同的方式编写/解决它。这是一个演示如何解决它。 迄今为止最好最优雅的解决方案!谢谢大哥!【参考方案3】:

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

工作示例:

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

【讨论】:

对我来说最好的答案:) 我们可以用这个方法检查子元素是否为空,如果是则隐藏父元素吗?我试过了,没有运气 @KavindaJayakody 是的,这将检查子元素是否为空。显示您的代码。 *ngIf="!ref.innerHTML.trim()" 这部分会导致性能问题。修剪是 O(n)。 @RandomCode 是对的,该函数会被多次调用。更好的方法是检查element.children.lengthelement.childnodes.length,具体取决于您要询问的内容。【参考方案4】:

当你注入内容时添加一个引用变量:

<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。无法理解为什么最佳答案获得了这么多票。 OP 询问 ngContent 是否为空 - 这意味着 &lt;MyContainer&gt;&lt;/MyContainer&gt;。您的解决方案希望用户在 MyContainer 下创建一个子元素:&lt;MyContainer&gt;&lt;div #content&gt;&lt;/div&gt;&lt;/MyContainer&gt;。虽然这是一种可能性,但我不会说这是优越的。【参考方案5】:

注入elementRef: ElementRef 并检查elementRef.nativeElement 是否有任何孩子。这可能只适用于encapsulation: ViewEncapsulation.Native

包装&lt;ng-content&gt; 标签并检查它是否有子标签。这不适用于encapsulation: ViewEncapsulation.Native

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

并检查它是否有任何孩子

@ViewChild('contentWrapper') contentWrapper;

ngAfterViewInit() 
  contentWrapper.nativeElement.childNodes...

(未测试)

【讨论】:

由于使用了@ViewChild,我对此投了反对票。虽然“合法”,但不应使用 ViewChild 访问模板中的子节点。这是一种反模式,因为虽然父母可能了解孩子,但他们不应该与孩子耦合,因为这通常会导致病理耦合;更合适的数据传输或请求处理形式是使用事件驱动方法 @Cody 感谢您对您的反对意见发表评论。其实我不认同你的说法。模板和组件类是一个单一的单元——一个组件。我不明白为什么从代码访问模板应该是一种反模式。 Angular (2/4) 与 AngularJS 几乎没有任何共同之处,只是两者都是 Web 框架和名称。 Gunter,你提出了两个非常好的观点。 Angular 不一定要带着 AngularJS 的污名。但我要指出,第一点(以及我在上面所做的那些)属于工程学的哲学阴沟。也就是说,我已经成为软件耦合方面的专家,我建议不要[至少大量]使用@ViewChild。感谢您为我的 cmets 增加了一些平衡——我发现我们的两个 cmets 和另一个一样值得考虑。 @Cody 也许你对@ViewChild() 的强烈看法来自这个名字。 “孩子”不一定是孩子,它们只是组件的声明部分(如我所见)。如果为此标记创建的组件实例是访问,这当然是另一回事,因为它至少需要了解它们的公共接口,这实际上会产生紧密耦合。这是一个非常有趣的讨论。让我担心的是我没有足够的时间来考虑这些问题,因为使用这样的框架往往会浪费时间来寻找任何方法。 不在 API 中公开它是真正的反模式 :-(【参考方案6】:

如果您想显示默认内容,为什么不直接使用 css 中的 'only-child' 选择器。

https://developer.mozilla.org/en-US/docs/Web/CSS/:only-child

例如: HTML

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

css

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

【讨论】:

相当聪明的解决方案,如果你不想处理 ViewChild 和组件中的东西。我喜欢! 请注意,transclude 内容必须是 HTML 节点(不仅仅是文本)【参考方案7】:

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

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

简单的css作品:

.wrapper 
  display: inline-block;

  &:empty 
    display: none;
  

【讨论】:

【参考方案8】:

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

根据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 的 form-field 组件中发现了这个解决方案在角度组件中实现。

在稍后注入组件内容的情况下,应用初始化之后,我们也可以使用响应式实现,通过订阅@987654330 的changes 事件@:

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

【讨论】:

【参考方案9】:

在 Angular 10 中,它略有变化。你会使用:

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

【讨论】:

【参考方案10】:

&lt;ng-content #ref&gt;&lt;/ng-content&gt; 显示错误“ref”未声明。 以下适用于 Angular 11(也可能是 10):

<div #ref><ng-content></ng-content></div>
  <ng-container *ngIf="!ref.hasChildNodes()">
       Default Content
  </ng-container>

【讨论】:

【参考方案11】:

在 Angular 12 中,控制台会为我报告以下内容:

“HTMLElement”类型上不存在属性“nativeElement”

似乎存在一个特定属性childElementCount,您可以将其用于这种情况。

因此,我成功地使用了它,它不会将动态内容包装到其他元素/标签中:

<div class="container">
  <!-- some html skipped -->

  <ng-container #someContent>
    <ng-content></ng-content>
  </ng-container>
  <span 
    *ngIf="
      someContent.childElementCount === undefined || 
      someContent.childElementCount === 0
    "
  >
    Display this if ng-content is empty!
  </span>

  <!-- some html skipped -->
</div>

【讨论】:

不知何故 hasChildNodes() 对我不起作用。这有帮助【参考方案12】:

2021 年 9 月

如果没有从实现组件中提供,还有另一种技术可以通过使用*ngTemplateOutlet 指令来完成默认内容,这使我们可以对自定义进行更多控制:

源组件中的示例

@Component(
  selector: 'feature-component',
  templateUrl: './feature-component.component.html',
)
export class FeatureComponent 
  @ContentChild('customTemplate') customTemplate: TemplateRef<any>;

然后在 HTML 模板中:

<ng-container
  [ngTemplateOutlet]="customTemplate || defaultTemplate"
></ng-container>

<ng-template #defaultTemplate>
  <div class="default">
    Default content...
  </div>
</ng-template>

目标组件:

<!-- default content -->

<feature-component></feature-component>


<!-- dynamic content -->

<feature-component>
  <ng-template #customTemplate>
    <div> Custom group items. </div>
  </ng-template>
</feature-component>

【讨论】:

这对我不起作用。 @Unknown 我可以知道你遇到的问题吗?【参考方案13】:

在 angular 11 中,我使用它并且工作正常。

模板文件:

 <h3 class="card-label" #titleBlock>
      <ng-content select="[title]" ></ng-content>
 </h3>

组件:

@ViewChild('titleBlock') titleBlock: ElementRef;
hasTitle: boolean;


ngAfterViewInit(): void 
    if (this.titleBlock && this.titleBlock.nativeElement.innerHTML.trim().length > 0)
    
        this.hasTitle=  true;
    
    else
    
       this.hasTitle=  false;
    

【讨论】:

【参考方案14】:

这个解决方案对我有用(在 Angular 版本 12.0.2 上)。 请注意,如果您的内容是动态的并且在组件已加载后从空变为非空(或其他方式),这可能不起作用。 这可以通过在ngOnChanges 中添加更改hasContent 的代码来解决。

例子:

import Component, ViewChild, AfterViewInit from '@angular/core';

@Component(
    selector: 'my-component',
    template: '<div [ngClass]="[hasContent ? 'has-content' : 'no-content']">
        <span #contentRef>
            <ng-content></ng-content>
        </span>
    </div>']
)
export class Momponent implements AfterViewInit 

    @ViewChild('contentRef', static: false) contentRef;

    hasContent: boolean;

    ngAfterViewInit(): void 
        setTimeout(() => 
            this.hasContent = this.contentRef?.nativeElement?.childNodes?.length > 1;
        );
    

【讨论】:

以上是关于如何检查 <ng-content> 是不是为空? (到目前为止在 Angular 2+ 中)的主要内容,如果未能解决你的问题,请参考以下文章

如何在JBoss中检查数据源?

如何检查和设置 JBoss 5 的 URL 和端口号?

JBoss 4.2.3 - 如何找到 jar 文件的路径

如何使用 Cargo maven 插件远程部署 EAR 到 JBoss 5.1.0.GA?

关于JBoss的一些项目配置

如何在 JBOSS EAP 7.1 中设置管理员用户(以便能够在紧急情况下通过 GUI 检查服务器?)?