angular content projection

Posted by zzlong on Sat, 05 Mar 2022 05:32:51 +0100

1, Ng content for content projection

1.1 ng-content

The ng content element is a placeholder for inserting external or dynamic content. The parent component passes the external content to the child component. When Angular parses the template, it will insert the external content where ng content appears in the child component template.

We can use content projection to create reusable components. These components have similar logic and layout and can be used in many places. Generally, we often use it when encapsulating some common components.

1.2 why use content projection

Define a button component:

button-component.ts

@Component({
    selector: '[appButton]',
    template: `
    <span class="icon-search"></span>
`
})
export class AppButtonComponent {}

The purpose of this button component is to add a search icon inside the button. We actually use the following:

<button appButton>
click
</button>

We found that the component will only display the search icon, and the text of the button will not be displayed. You can think of the most commonly used @ Input decorator, but what if we don't just want to pass in the text, but a piece of html? In this case, ng content will be used.

1.2 single slot content projection

The most basic form of content projection is single slot content projection.

Single slot content projection refers to the creation of a component in which we can project a component.

Take the button component as an example to create a single slot content projection:

button-component.ts

@Component({
    selector: '[appButton]',
    template: `
    <span class="icon-search"></span> <ng-content></ng-content>
`
})
export class AppButtonComponent {}

The actual use is as follows:

<button appButton>
click
</button>

It can be found that the effect of this button component is to display both the search icon and the button text (click). That is, the content in the middle of < button appbutton > < / button > is projected to the < ng content > < / ng content > position of the component.

The ng content element is a placeholder that does not create a real DOM element. Those custom attributes of NG content will be ignored.

1.3 multi slot content projection

A component can have multiple slots, and each slot can specify a CSS selector that determines what content to put into the slot. This mode is called multi slot content projection. Using this mode, we must specify where we want the projected content to appear. You can do this by using the select attribute of NG content.

  • The component template contains multiple ng content tags.
  • In order to distinguish that the projected content can be projected to the corresponding ng content tag, you need to use the select attribute on the ng content tag as the identification.
  • The select attribute supports any combination of tag name, attribute, CSS class and: not pseudo class.
  • The ng content tag without the select attribute will be used as the default slot. All unmatched projected content will be projected at the location of the ng content.

Take the button component as an example to create a multi slot content projection:

button-component.ts

@Component({
    selector: '[appButton]',
    template: `
    <span class="icon-search"></span> <ng-content select="[shuxing]"></ng-content> <ng-content select="p"></ng-content> <ng-content select=".lei"></ng-content>
`
})
export class AppButtonComponent {}

The actual use is as follows:

<button appButton>
<p>click</p> <span shuxing>me</span> <span class="lei">here</span>
</button>

1.4 ngProjectAs

In some cases, we need to use ng container to wrap some content and pass it to the component. In most cases, we need to use structured instructions, such as ngIf or ngSwitch..

In the following example, we wrap the header in the ng container.

@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>
`
})
export class AppCardComponent {}

use:

<app-card>
  <ng-container>
    <header>
      <h1>Angular</h1>
    </header>
  </ng-container>
  <content>One framework. Mobile & desktop.</content>
  <footer><b>Super-powered by Google </b></footer>
</app-card>

Due to the existence of NG container, the header part is not rendered to the slot we want to render, but to the ng content that does not provide any selector.
In this case, we can use the ngProjectAs attribute.
Add this attribute to the ng container above to render as we expect.

<app-card>
  <ng-container ngProjectAs='header'>
    <header>
      <h1>Angular</h1>
    </header>
  </ng-container>
  <content>One framework. Mobile & desktop.</content>
  <footer><b>Super-powered by Google </b></footer>
</app-card>

2, Conditional content projection

If your component needs to conditionally render content or render content multiple times, you should configure the component to accept an ng template element containing the content to be conditionally rendered.

In this case, the ng content element is not recommended, because as long as the user of the component provides content, even if the component has never defined an ng content element or the ng content element is located inside the ngIf statement, the content will always be initialized.

Using the ng template element, you can make the component explicitly render the content according to any conditions you want, and you can render multiple times. Angular does not initialize the contents of an ng template element until it is explicitly rendered.

2.1 ng-container

It is neither a component nor an instruction, but just a special tag. Use ng container to render the content of the template, not itself.

  • angular code snippet
<div>
  <ng-container>
    <p>My name is wyl.</p>
    <p>What is you name?</p>
  </ng-container>
</div>
  • In the browser debugging window, you can find that the < ng container > tag has disappeared and has no effect
<div>
  <p>My name is wyl.</p>
  <p>What is you name?</p>
</div>
  • The usage scenario is as follows. When we need to traverse or if judge, it can act as a carrier
<ul>
  <ng-container *ngFor="let item of items">
    <li>{{ item .name}}</li>
    <li>{{ item .age}}</li>
    <li>{{ item .sex}}</li>
  </ng-container>
</ul>

In addition, for and if, one of the common errors in ng, cannot be written on the same tag (only one structural instruction can be applied to a host element). Using ng container tag can reduce the nesting of levels on the basis of realizing functions.

2.2 ng-template

Let's start with the following code

<ng-template>
    <p> In template, no attributes. </p>
</ng-template>

<ng-container>
    <p> In ng-container, no attributes. </p>
</ng-container>

The browser output is:

In ng-container, no attributes.

That is, the content in < ng template > will not be displayed. When adding ngIf instruction to the above template:

<ng-template [ngIf]="true">
   <p> ngIf with a ng-template.</p>
</ng-template>

<ng-container *ngIf="true">
   <p> ngIf with an ng-container.</p>
</ng-container>

The browser output is:

ngIf with a ng-template.
ngIf with an ng-container.

2.3 combination of NG template and < ng container >

<ng-container *ngIf="showSearchBread; else tplSearchEmpty">
     The data you want can't be searched for now
</ng-container>
<ng-template #tplSearchEmpty>
     Get started!
</ng-template>

2.4 ngTemplateOutlet

Insert an embedded view created by ng template. ngTemplateOutlet is a structural instruction that receives a value of TemplateRef type;

<div *ngTemplateOutlet="tpl1"></div>
<ng-template #tpl1>
  <span>I am span in template {{title}}</span>
</ng-template>

*ngTemplateOutlet = "templateRefExp; content: contentExp "

  • Templaterefexp: the #ID of the ng template element
  • contextExp:
    1. Nullable parameter
    2. content is an object that can contain a $implicit key as the default value. When used, it is bound with a let key statement in the template
    3. The non default field of content needs to be bound with the let templatekey = contentkey statement

Use the following:

@Component({
  selector: 'ng-template-outlet-example',
  template: `
    <ng-container *ngTemplateOutlet="greet"></ng-container>
    <hr>
    <ng-container *ngTemplateOutlet="eng; context: myContext"></ng-container>
    <hr>
    <ng-container *ngTemplateOutlet="svk; context: myContext"></ng-container>
    <hr>
    <ng-template #greet><span>Hello</span></ng-template>
    <ng-template #eng let-name><span>Hello {{name}}!</span></ng-template>
    <ng-template #svk let-person="localSk"><span>Ahoj {{person}}!</span></ng-template>
`
})
class NgTemplateOutletExample {
  myContext = {$implicit: 'World', localSk: 'Svet'};
}

2.5 content projection using ngTemplateOutlet

@Component({
    selector: 'app-card',
    template: `
		<div class="card">
		  <div class="header">
		  	<ng-container *ngTemplateOutlet="headerTemplate; context: { $implicit: title, index: otherDate }"></ng-container>
		  </div>
		</div>
`
})
export class AppCardComponent {

	 @ContentChild('header', { static: true }) headerTemplate: TemplateRef<any>;

	 public title = 'Test';
	 public otherDate = {
	 	auth: 'me',
	 	name: 'appCard'
	 };
}

use

<app-card>
  <ng-template #header let-label let-item="otherDate">
    <h1>Angular</h1> {{label}} (Test) and  {{otherDate | json}} ({auth: 'me', name: 'appCard'})
  </ng-template>
</app-card>

reference resources

Topics: Front-end angular