【问题标题】: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 的情况下做到这一点?

【问题讨论】:

  • 仅供参考,我知道接受的答案有效,但我认为将“useDefault”类型的输入参数传递给组件会更好,默认为 false。

标签: javascript angular


【解决方案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 中,则提供默认内容。

可能的选择器:

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

    这个需要更少的代码/标记。从 IE 9 开始支持:Can I Use :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>

【讨论】:

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

    我在 matSuffixform-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;
                    });
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2016-12-06
              • 2021-07-25
              • 2017-07-19
              • 2019-04-22
              • 2018-12-23
              • 1970-01-01
              • 2020-08-24
              相关资源
              最近更新 更多