How to check whether <ng-content> is empty? (in Angular 2+ till now)
JavascriptAngularJavascript Problem Overview
Suppose I have a component:
@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 {
}
Now, I would like to display some default content if <ng-content>
for this component is empty. Is there an easy way to do this without accessing the DOM directly?
Javascript Solutions
Solution 1 - Javascript
Wrap ng-content
in an HTML element like a div
to get a local reference to it, then bind the ngIf
expression to 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>`
>Updated for Angular 12; old logic ("ref.nativeElement.childNodes.length
") gives error, as nativeElement
is undefined
nowadays.
Solution 2 - Javascript
EDIT 17.03.2020
Pure CSS (2 solutions)
Provides default content if nothing is projected into ng-content.
Possible selectors:
-
:only-child
selector. See this post here: :only-child SelectorThis one require less code / markup. Support since IE 9: Can I Use :only-child
-
:empty
selector. Just read further.Support from IE 9 and partially since IE 7/8: https://caniuse.com/#feat=css-sel3</sup>
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;
}
In case it's not working
Be aware of, that having at least one whitespace is considered to not beeing empty. Angular removes whitespace, but just in case if it is not:
<div class="wrapper"><!--
--><ng-content select="my-component"></ng-content><!--
--></div>
or
<div class="wrapper"><ng-content select="my-component"></ng-content></div>
Solution 3 - Javascript
There some missing in @pixelbits answer. We need to check not only children
property, because any line breaks or spaces in parent template will cause children
element with blank text\linebreaks.
Better to check .innerHTML
and .trim()
it.
Working example:
<span #ref><ng-content></ng-content></span>
<span *ngIf="!ref.innerHTML.trim()">
Content if empty
</span>
Solution 4 - Javascript
When you inject the content add a reference variable:
<div #content>Some Content</div>
and in your component class get a reference to the injected content with @ContentChild()
@ContentChild('content') content: ElementRef;
so in your component template you can check if the content variable has a value
<div>
<ng-content></ng-content>
<span *ngIf="!content">
Display this if ng-content is empty!
</span>
</div>
Solution 5 - Javascript
If you want to display a default content why dont you just use the 'only-child' selector from css.
https://developer.mozilla.org/en-US/docs/Web/CSS/:only-child
for eg: HTML
<div>
<ng-content></ng-content>
<div class="default-content">I am default</div>
</div>
css
.default-content:not(:only-child) {
display: none;
}
Solution 6 - Javascript
Inject elementRef: ElementRef
and check if elementRef.nativeElement
has any children. This might only work with encapsulation: ViewEncapsulation.Native
.
Wrap the <ng-content>
tag and check if it has children. This doesn't work with encapsulation: ViewEncapsulation.Native
.
<div #contentWrapper>
<ng-content></ng-content>
</div>
and check if it has any children
@ViewChild('contentWrapper') contentWrapper;
ngAfterViewInit() {
contentWrapper.nativeElement.childNodes...
}
(not tested)
Solution 7 - Javascript
Sep 2021
There is another technique to accomplish the default content if not provided from the implementation component by using *ngTemplateOutlet
directive which allows us to have the customization more control:
Example in source component:
import { Component, ContentChild, TemplateRef } from '@angular/core';
@Component({
selector: 'feature-component',
templateUrl: './feature-component.component.html',
})
export class FeatureComponent {
@ContentChild('customTemplate') customTemplate: TemplateRef<any>;
}
Then in HTML template:
<ng-container
[ngTemplateOutlet]="customTemplate || defaultTemplate"
></ng-container>
<ng-template #defaultTemplate>
<div class="default">
Default content...
</div>
</ng-template>
target component:
<!-- default content -->
<feature-component></feature-component>
<!-- dynamic content -->
<feature-component>
<ng-template #customTemplate>
<div> Custom group items. </div>
</ng-template>
</feature-component>
Solution 8 - Javascript
In my case I have to hide parent of empty ng-content:
<span class="ml-1 wrapper">
<ng-content>
</ng-content>
</span>
Simple css works:
.wrapper {
display: inline-block;
&:empty {
display: none;
}
}
Solution 9 - Javascript
I've implemented a solution by using @ContentChildren decorator, that is somehow similar to @Lerner's answer.
According to docs, this decorator:
> Get the QueryList of elements or directives from the content DOM. Any time a child element is added, removed, or moved, the query list will be updated, and the changes observable of the query list will emit a new value.
So the necessary code in the parent component will be:
<app-my-component>
<div #myComponentContent>
This is my component content
</div>
</app-my-component>
In the component class:
@ContentChildren('myComponentContent') content: QueryList<ElementRef>;
Then, in component template:
<div class="container">
<ng-content></ng-content>
<span *ngIf="*ngIf="!content.length""><em>Display this if ng-content is empty!</em></span>
</div>
Full example: https://stackblitz.com/edit/angular-jjjdqb
I've found this solution implemented in angular components, for matSuffix
, in the form-field component.
In the situation when the content of the component is injected later on, after the app is initialised, we can also use a reactive implementation, by subscribing to the changes
event of the 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();
}
}
Full example: https://stackblitz.com/edit/angular-essvnq
Solution 10 - Javascript
With Angular 10, it has changed slightly. You would use:
<div #ref><ng-content></ng-content></div>
<span *ngIf="ref.children.length == 0">
Display this if ng-content is empty!
</span>
Solution 11 - Javascript
<ng-content #ref></ng-content>
shows error "ref" is not declared.
The following is working in Angular 11 (Probably 10 also):
<div #ref><ng-content></ng-content></div>
<ng-container *ngIf="!ref.hasChildNodes()">
Default Content
</ng-container>
Solution 12 - Javascript
In Angular 12, the console reports the following for me:
> Property 'nativeElement' does not exist on type 'HTMLElement'
There seems to exist a specific attribute childElementCount
which you can use for this case.
As a consequence, I used this successfully, which does not wrap the dynamic content into additional elements/tags:
<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>
Solution 13 - Javascript
in angular 11 I use this and works fine.
template file :
<h3 class="card-label" #titleBlock>
<ng-content select="[title]" ></ng-content>
</h3>
component:
@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;
}
}
Solution 14 - Javascript
This solution has worked for me (on angular version 12.0.2).
Note that this will probably not work if your content is dynamic and changes from empty to non-empty (or the other way) after the component was already loaded.
That can be fixed by adding code that changes hasContent
inside ngOnChanges
.
Example:
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;
});
}
}