如何检查 <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
现在,如果此组件的<ng-content>
为空,我想显示一些默认内容。有没有一种简单的方法可以在不直接访问 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
") 会出错,因为如今nativeElement
是undefined
。
【讨论】:
没有别的办法吗?因为这很难看,与 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 进行查询,但用作结构指令,因此仅使用标记就不会涉及初始引导):<loader [data]="allDataWeNeed"> <desired-component *loadingRender><desired-component> <other-default-component *renderWhenEmptyResult></other-default-component> </loader>
和加载组件,您可以在数据加载时显示感知的性能加载。您可以用不同的方式编写/解决它。这是一个演示如何解决它。
迄今为止最好最优雅的解决方案!谢谢大哥!【参考方案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.length
或element.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 是否为空 - 这意味着<MyContainer></MyContainer>
。您的解决方案希望用户在 MyContainer 下创建一个子元素:<MyContainer><div #content></div></MyContainer>
。虽然这是一种可能性,但我不会说这是优越的。【参考方案5】:
注入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...
(未测试)
【讨论】:
由于使用了@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】:<ng-content #ref></ng-content>
显示错误“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+ 中)的主要内容,如果未能解决你的问题,请参考以下文章