Slawomir Dadas
Slawomir Dadas

Reputation: 2208

How to check whether <ng-content> is empty? (in Angular 2+ till now)

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?

Upvotes: 175

Views: 113868

Answers (20)

Denes Papp
Denes Papp

Reputation: 3992

Since Angular 18:

From the docs:

Angular can show fallback content for a component's placeholder if that component doesn't have any matching child content. You can specify fallback content by adding child content to the element itself.

<!-- Component template -->
<div class="card-shadow">
  <ng-content select="card-title">Default Title</ng-content>
  <div class="card-divider"></div>
  <ng-content select="card-body">Default Body</ng-content>
</div>

Upvotes: 1

Chellappan வ
Chellappan வ

Reputation: 27409

Update

In Angular v18, we can provide default content for ng-content to be displayed if no projected content is provided within an ng-content slot:

template: `<div><ng-content>Display this if ng-content is empty!</ng-content></div>`
      

Upvotes: 2

epelc
epelc

Reputation: 5764

This is supported as of angular 18.

https://github.com/angular/angular/issues/12530

https://www.youtube.com/watch?v=-fdKyT_CZso

Simply put your default content inside of ng-content.

<ng-content>
  <p>My default/empty content</p>
</ng-content>

Upvotes: -1

Enea Jahollari
Enea Jahollari

Reputation: 306

In Angular v18, we get support for fallback content for ng-content (if no content was projected).

Copied from PR:

Adds the ability to specify content that Angular should fall back to if nothing is projected into an ng-content slot. For example, if we have the following setup

@Component({
  selector: 'my-comp',
  template: `
    <ng-content select="header">Default header</ng-content>
    <ng-content select="footer">Default footer</ng-content>
  `
})
class MyComp {}

@Component({
  template: `
    <my-comp>
      <footer>New footer</footer>
    </my-comp>
  `
})
class MyApp {}

The instance of my-comp in the app will have the default header and the new footer.

Note: Angular's content projection happens during creation time. This means that dynamically changing the contents of the slot will not cause the default content to show up, e.g. if a if block goes from true to false.

Upvotes: 0

Newbie
Newbie

Reputation: 376

Just thought I'd share. In my particular instance, I did not want any wrapper div elements and had to purely use ng-container or ng-template.

component.ts:

@Component({
    selector: 'component',
    templateUrl: './component.html',
})
export class AitStoryDivComponent implements AfterViewInit {

    constructor(public ref: ElementRef) { }

    showContent = true;

    ngAfterViewInit(): void {
        this.showContent = this.ref.nativeElement.children.length > 0
    }

}

component.html

<ng-template #textTemplate>
    Some default text if component does not have ng-content
</ng-template>

<ng-container *ngIf="showContent; else textTemplate">
    <ng-content></ng-content>
</ng-container>

Upvotes: -1

Matt D
Matt D

Reputation: 3496

There are a lot of good answers here, though a working solution seems to change through the years.

In Angular 15, the following seems to work best for me, self-contained and concise:

<div #content><ng-content></ng-content></div>
<div *ngIf="!content.childElementCount">
    <h1>Default Content Goes Here!</h1>
</div>

Upvotes: -1

Ravi Anand
Ravi Anand

Reputation: 5534

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>

Upvotes: 13

Shahar
Shahar

Reputation: 2347

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;
        });
    }
}

Upvotes: 2

Michael Kang
Michael Kang

Reputation: 52867

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.

Upvotes: 164

Stefan Rein
Stefan Rein

Reputation: 9062

EDIT 17.03.2020

Pure CSS (2 solutions)

Provides default content if nothing is projected into ng-content.

Possible selectors:

  1. :only-child selector. See this post here: :only-child Selector

    This one require less code / markup. Support since IE 9: Can I Use :only-child

  2. :empty selector. Just read further.

    Support from IE 9 and partially since 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;
}

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>

Upvotes: 89

Michael
Michael

Reputation: 1035

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>

Upvotes: 2

Fadakar
Fadakar

Reputation: 569

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;
    }
}

Upvotes: 2

ecosystem31
ecosystem31

Reputation: 316

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;
}

Upvotes: 22

Deb
Deb

Reputation: 5649

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

Upvotes: 3

John Hamm
John Hamm

Reputation: 514

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>

Upvotes: 4

andreivictor
andreivictor

Reputation: 8471

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

Upvotes: 3

smg
smg

Reputation: 1216

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;
  }
}

Upvotes: 4

Lerner
Lerner

Reputation: 1171

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> 

Upvotes: 34

lfoma
lfoma

Reputation: 621

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>

Upvotes: 52

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657741

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)

Upvotes: 14

Related Questions