Ng-Content & Content Projection In Angular

Ng-Content & Content Projection In Angular

In this article, we will learn how to use ng-content to add external content (content projection) to the Template. We know how to transmit data from the parent component to the child component by using the @Input decorator. However, it is restricted to data. We can't use that technique to transmit content to the child component, which includes HTML elements, CSS, and so on. We'll need to employ content projection to accomplish this.

The HTML content is passed from the parent component to the child component using content projection. The template will be shown in a specific location by the child component. The ng-content element is used to identify a location in the child component's template. The selector attribute in ng-content also allows us to construct several slots. Each slot can receive distinct content from the parent.

What is ng-content

External or dynamic content can be inserted using the ng-content tag as a placeholder. The external content is passed from the parent component to the child component. When Angular parses the template, it adds the external content in the child component's template where ng-content appears.

Content projection can be used to make a reusable component. Components with comparable logic and layout that can be used in multiple places throughout the application.

Consider the case of a card component. It comprises three sections: a header, a footer, and a body. These categories will have a variety of content. We may use ng-content to send these parts from the parent component to the card component. This allows us to use the card component across the application.

Without ng-content

Let's develop a basic button component without ng-content first to see how content projection with ng-content works.

Create an Angular application from scratch. btn.component.ts is a new component. It's a straightforward component that shows a button with the caption Click Me.

import { Component } from '@angular/core';
 
@Component({
 selector: 'app-btn',
 template: `<button>
       Click Me
     </button>`
})
export class BtnComponent {
}

Go to app.component.html and add the following code.

<h2>Simple Button Demo</h2>
<app-btn></app-btn>
<app-btn></app-btn>

Two buttons with the caption Click Me appear on the screen as intended in the code above.

What if we want to edit the parent's caption? Using the @Input attribute, we can accomplish this. However, with @input, we can just change the button's caption. However, we are unable to alter the caption's appearance.

ng-content Example

FancyBtnComponent is a new component. Except for one adjustment, copy all of the codes from BtnComponent. Remove Instead, click Me and type <ng-content> </ng-content>. This tag serves as a placeholder for other tags. You can also think of it as a component argument. The argument must be supplied by the parent component.

import { Component, Output, EventEmitter } from '@angular/core';
 
@Component({
 selector: 'app-fancybtn',
 template: `
     <button>
       <ng-content></ng-content>
     </button> `
})
export class FancyBtnComponent {
}

Now, Open the app.component.html file and add the following code.

<h2>Button Demo With ng-content</h2>
<app-fancybtn>Click Me</app-fancybtn>
<app-fancybtn><b>Submit</b></app-fancybtn>

Our FancyBtnComponent receives the content between <app-fancybtn> and </app-fancybtn>. It is displayed in place of ng-content by the component.

The benefit of such a method is that any HTML content can be passed.

Events

Clicks, inputs, and other events rise to the surface. As a result, the parent can be recorded as indicated below.

Add the following code under the app.component.html:

<h2>Button with click event</h2>
<app-fancybtn btnclicked="" click="" event=""><b>Submit</b></app-fancybtn>

Add the following code under the App.component.ts:

btnClicked($event) {
  console.log($event)
  alert('button clicked')
}

However, if you have multiple buttons, you may need to look at the $event argument to see which one is responsible for the event.

Custom Events

You can use the @output to construct custom events, as illustrated below.

@Output() someEvent:EventEmitter =new EventEmitter();
 
raiseSomeEvent() {
 this.someEvent.emit(args);
}

component of the parent:

<app-fancybtn event="" osomething="" someevent=""><b>Submit</b></app-fancybtn>

Multiple Projections using ng-content

The button example is a fairly straightforward one. That is far from the case with ng-content. It gives us the ability to make many slots in the template. A selector must be defined for each slot. You can think of this as a component with several arguments.

We can build various contents in the parent component, and each of those contents can be projected into any of the slots based on their selector. The ng-content Select property is used to accomplish this. A CSS Selector is the select attribute.

Example of the ng-content select attribute

Create a new component card, namely as card.component.ts:

import { Component } from '@angular/core';
 
 
@Component({
  selector: 'app-card',
  template: `
    <div class="card">
    <div class="header">
      <ng-content select="header"></ng-content>
    </div>
    <div class="content">
      <ng-content select="content"></ng-content>
    </div>
    <div class="footer">
      <ng-content select="footer"></ng-content>
    </div>
    </div>
  `,
  styles: [
    ` .card { min- width: 280px;  margin: 5px;  float:left  } 
      .header { color: blue}
    `
  ]
})
export class CardComponent {
}

We have three ng-content slots in the example above, each with a selector header, content, and footer.

Now open the app.component.html file and paste the code below:

<app-card>
 <header><h1>Angular</h1></header>
 <content>One framework. Mobile &amp; desktop.</content>
 <footer><b>Super-powered by Google </b></footer>
</app-card>
  
<app-card>
 <header><h1 style="color: red;">React</h1></header>
 <content>A JavaScript library for building user interfaces</content>
 <footer><b>Facebook Open Source </b></footer>
</app-card>

The select attribute is a CSS selector

As the chosen attribute, you can use any CSS selector. Class, element, and id attributes, for example. For instance, the card component above uses the CSS class

import { Component } from '@angular/core';
 
@Component({
  selector: 'card',
  template: `
    <div class="card">
    <div class="header">
      <ng-content select=".header"></ng-content>
    </div>
    <div class="content">
      <ng-content select=".content"></ng-content>
    </div>
    <div class="footer">
      <ng-content select=".footer"></ng-content>
    </div>
    </div>
  `,
  styles: [
    ` .card { width: 280px;  margin: 5px;  float:left; border-width:1px; border-style:solid ; } 
      .header { color: blue}
    `
  ]
})
export class CardComponent {

We use it as well in the component.

<card>
  <div class="header">
    <h1>Angular</h1>
  </div>
  <div class="content">One framework. Mobile &amp; desktop.</div>
  <div class="footer"><b>Super-powered by Google </b></div>
</card>
 
<card>
  <div class="header">
    <h1 style="color: red;">React</h1>
  </div>
  <div class="content">A JavaScript library for building user interfaces</div>
  <div class="footer"><b>Facebook Open Source </b></div>
</card>

Similarly, as illustrated below, you can use the various CSS Selectors.

<ng-content select="custom-element"></ng-content>
<ng-content select=".custom-class"></ng-content>
<ng-content select="[custom-attribute]"></ng-content>

ng-content without selector catches all

The last paragraph in the following example does not correspond to any ng-content slots. As a result, ng-content will not project the last para since it is unable to determine where to place it.

<card>
  <div class="header"><h1>Typescript</h1></div>
  <div class="content">Typescript is a javascript for any scale</div>
  <div class="footer"><b>Microsoft </b></div>
  <p>This text will not be shown</p>
</card>

We can use ng-content without any selection to fix the problem described previously. It will show all of the stuff that can't be projected into any of the other slots.

import { Component } from '@angular/core';
 
 
@Component({
  selector: 'app-card',
  template: `
    <div class="card">
    <div class="header">
      <ng-content select="header"></ng-content>
    </div>
    <div class="content">
      <ng-content select="content"></ng-content>
    </div>
    <div class="footer">
      <ng-content select="footer"></ng-content>
    </div>
    <ng-content></ng-content>                      
    </div>
  `,
  styles: [
    ` .card { min- width: 280px;  margin: 5px;  float:left  } 
      .header { color: blue}
    `
  ]
})
export class CardComponent {
}

ngProjectAs

Using the ng-container to wrap the component is sometimes necessary. When you use a structural directive like ngIf or ngSwitch, most of the time.

The header is enclosed within the ng-container in the following example.

<card>
  <ng-container>
    <div class="header">
      <h1 style="color: red;">React</h1>
    </div>
  </ng-container>
  <div class="content">A JavaScript library for building user interfaces</div>
  <div class="footer"><b>Facebook Open Source </b></div>
</card>

The header section is not projected to the header slot due to the ng-container. It is instead projected to the ng-content slot, which lacks a selector.

You can use the ngProjectAs attribute to help in this situation, as demonstrated below.

<card>
  <ng-container ngprojectas="header">
    <div>
      <h1 style="color: red;">React</h1>
    </div>
  </ng-container>
  <div class="content">A JavaScript library for building user interfaces</div>
  <div class="footer"><b>Facebook Open Source </b></div>
</card>

Conclusion

We can use ng-content to include external stuff in the Template. Unlike @Input, we can give data to ng-content, including HTML elements, CSS, and so on. This is also known as content projection. The selector attribute can also be used to establish various slots. We can add various materials to different slots using these slots.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in the comment section.

Post a Comment

Previous Post Next Post