Angular 8: detect if a ng-content has content in it (or exists)

Viewed 8772

I have a component whose template allows for 2 content areas: Text and "read more" text. If the consumer of the component adds the area for the "read more" text, I want to show the "read more" link the end-user would click to show the text. If they don't include/need any "read more" text I don't want to show the link.

How do I detect the presence of the template area, and act accordingly with an ngIf?

For example, the html might be:

<app-promohero-message-unit title="Title for messaging module">
     <div description>
       Include a short, informative description here.
     </div>
     <div readmoretext>
       If you need to add more detail, include another sentence or two it in this section.
     </div>
</app-promohero-message-unit>

Obviously, they might not need readmoretext, so if they've omitted it I should not show the readmore link.

The component code is, so far:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-promohero-message-unit',
  template: `
    <div>
      <h3 class="text-white">{{ title }}</h3>
      <p class="text-white">
        <ng-content select="[description]"></ng-content>
      </p>
      <p class="text-white" *ngIf="readMore">
        <ng-content select="[readmoretext]"></ng-content>
      </p>
    </div>
    <p>
      <a class="text-white" (click)="showReadMore()" *ngIf="something"><u>Read more</u></a>
    </p>
  `
})
export class PromoheroMessageUnitComponent {
  @Input()
  title: string;

  readMore = false;

  showReadMore() {
    this.readMore = true;
  }
}
3 Answers

In Angular 8 you dont have to use the ngAfterViewInit life cycle hook. You can use the ngOnInit as long as you set the "static" value of the viewchild to true.

import { Component, OnInit, ViewChild, TemplateRef, ElementRef } from '@angular/core';

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {
  @ViewChild('content', { read: ElementRef, static: true }) content: ElementRef;
  constructor() { }

  ngOnInit() {
    console.log(!!this.content.nativeElement.innerHTML);  // return true if there is a content
  }
}

Note that you must wrap the ng-content directive with html tag (such as div, span etc) and to set the templateRef on this outer tag.

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

I putted it on stackblitz: https://stackblitz.com/edit/angular-8-communicating-between-components-mzneaa?file=app/app.component.html

You can get a reference to the ng-content (Template Variable) and then access that variable in your component to check the length on the content of that ng-content using ViewChild

Then you can use the ngAfterViewInit life cycle hook to check for ng-content length

Your code will be like this:

import { Component, Input, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-promohero-message-unit',
  template: `
    <div>
      <h3 class="text-white">{{ title }}</h3>
      <p class="text-white">
        <ng-content select="[description]"></ng-content>
      </p>
      <p class="text-white" *ngIf="readMore">
        <ng-content #readMoreContent select="[readmoretext]"></ng-content>
      </p>
    </div>
    <p>
      <a class="text-white" (click)="showReadMore()" *ngIf="something"><u>Read more</u></a>
    </p>
  `
})
export class PromoheroMessageUnitComponent {
  @Input()
  title: string;
  @ViewChild('readMoreContent') readMoreContent: ElementRef;

  readMore = false;

  ngAfterViewInit() {
    if (this.readMoreContent.nativeElement.childNodes.length.value == 0){
      this.readMore = false
    }
  }

  showReadMore() {
    this.readMore = true;
  }
}

You can use the ContentChild decorator for this, but will need to use an ng-template with a defined id as your content:

<app-promohero-message-unit title="Title for messaging module">
     <div description>
       Include a short, informative description here.
     </div>
     <ng-template #readmoretext>
       If you need to add more detail, include another sentence or two it in this section.
     </ng-template>
</app-promohero-message-unit>

Then in your component, you can use the ContentChild annotation like this:

export class PromoheroMessageUnitComponent {

    @ContentChild('readmoretext')
    readMoreContent: TemplateRef<any>;

    // ...snip
}

Then finally in the HTML for your component:

      <!-- snip -->
      <p class="text-white" *ngIf="readMoreContent">
        <ng-container *ngTemplateOutlet="readMoreContent"></ng-container>
      </p>
Related