Angular - 内容投影



内容投影是 Angular 组件中的一种技术,它允许将外部内容(来自组件的使用者)与布局和样式一起包含在组件模板的特定区域中。一个简单的例子如下:

让我们考虑一个组件,content-projection-sample 具有以下模板:

<p>This is content from the component template</p>
   <ng-content></ng-content>
<p>This is another content from component template</p> 

可以使用以下方法来包含外部数据:

<content-projection-sample>
   <p>This is content external content</p>
</content-projection-sample>

组件的输出将如下所示:

<p>This is content from the component template</p>
<p>This is content external content</p>
<p>This is another content from component template</p> 

在这里,外部内容放置/投影在 ng-content 的位置。

让我们通过创建上面解释的组件来理解内容投影。

步骤 1:创建一个新的组件,content-projection-sample

$ ng generate component content-projection-sample
CREATE src/app/content-projection-sample/content-projection-sample.component.css (0 bytes)
CREATE src/app/content-projection-sample/content-projection-sample.component.html (40 bytes)
CREATE src/app/content-projection-sample/content-projection-sample.component.spec.ts (680 bytes)
CREATE src/app/content-projection-sample/content-projection-sample.component.ts (276 bytes)
UPDATE src/app/app.module.ts (701 bytes)

步骤 2:在模板 content-projection-sample.component.html 中添加 ng-content 标签,如下所示:

<p>This is content from the component template</p>
   <ng-content></ng-content>
<p>This is another content from component template</p> 

步骤 3:在 app 组件的 app.component.html 中使用 content-projection-sample 组件,如下所示:

<app-content-projection-sample>
   <p>This is external content</p>
</app-content-projection-sample>
<router-outlet></router-outlet>

步骤 4:运行应用程序并检查输出。

<app-content-projection-sample _ngcontent-ng-c2678441144="" _nghost-ng-c981906799="">
   <p _ngcontent-ng-c981906799=""> This is content from the component template</p>
   <p _ngcontent-ng-c2678441144=""> This is external content</p>
   <p _ngcontent-ng-c981906799=""> This is another content from component template</p>
</app-content-projection-sample>

步骤 5:应用程序的输出如下所示:

Application Output

多插槽内容投影

只包含一个内容投影的组件称为单插槽内容投影。Angular 也允许将多个内容投影到组件中,这称为多插槽内容投影。让我们看看如何通过修改上面的示例来使用多个内容投影。

  • 在组件模板 content-projection-sample.component.html 中添加另一个带有选择器属性的 ng-content,如下所示:

<p>This is content from the component template</p>
<ng-content></ng-content>
<p>This is another content from component template</p>
<ng-content select="[second]"></ng-content>
  • 更新 app 组件模板 app.component.html,如下所示:

<app-content-projection-sample>
   <p>This is external content</p>
   <p second>This is yet another external content</p>
</app-content-projection-sample>

<router-outlet></router-outlet>

这里,组件模板中设置的选择器属性(第二个)的值用于要投影的内容。

  • 现在,运行应用程序并检查输出。

<app-content-projection-sample _ngcontent-ng-c2332747330="" _nghost-ng-c2742507360="">
   <p _ngcontent-ng-c2742507360=""> This is content from the component template</p>
   <p _ngcontent-ng-c2332747330=""> This is external content</p>
   <p _ngcontent-ng-c2742507360=""> This is another content from component template</p>
   <p _ngcontent-ng-c2332747330="" second=""> This is yet another external content</p>
</app-content-projection-sample>
  • 应用程序的输出如下所示:

multi-slot content

ngProjectAs

ngProjectAs 是一个特殊的属性,用于在复杂场景中投影内容。一个例子是使用 ng-container 来布局模板。众所周知,ng-container 本身不渲染,而是渲染其子内容,我们需要使用 ngProjectAs 属性来投影其内容。

让我们更改上面的示例以使用 ng-container 和 ngProjectAs 属性。

  • 更新 app 组件模板,**app.component.html**,如下所示:

<app-content-projection-sample>
   <p>This is external content</p>
   <ng-container ngProjectAs="second">
      <p>This is yet another external content</p>
   </ng-container>
</app-content-projection-sample>

<router-outlet></router-outlet>

这里,组件模板中设置的选择器属性(第二个)的值用于 ng-container。

  • 现在,运行应用程序并检查输出。

<app-content-projection-sample _ngcontent-ng-c2332747330="" _nghost-ng-c2742507360="">
   <p _ngcontent-ng-c2742507360=""> This is content from the component template</p>
   <p _ngcontent-ng-c2332747330=""> This is external content</p>
   <p _ngcontent-ng-c2742507360=""> This is another content from component template</p>
   <p _ngcontent-ng-c2332747330="" second=""> This is yet another external content</p>
</app-content-projection-sample>
  • 应用程序的输出如下所示:

ngProjectAs

条件内容投影

条件内容投影是在满足特定条件时投影内容。我们可以使用 ng-content 来有条件地投影内容。但是不推荐这样做,因为即使内容不会被渲染,ng-content 也会被初始化。相反,我们可以使用 ng-template 来安全地投影内容,因为它只会在内容将被渲染时才初始化内容。

使用 ng-template 投影内容的总体思路如下:

步骤 1:创建一个指令(例如 TestDirective)来定位和选择 ng-template,它将用于存储/包含要投影的内容。该指令将在构造函数中初始化期间获取 ng-template 的引用。此模板引用将由父组件用来获取内容并在适当的位置渲染它。

constructor(public tmpl_ref: TemplateRef<unknown>) { }

步骤 2:使用 @ContentChild 装饰器在父组件(提供内容投影的组件)中获取指令实例,如下所示:

@ContentChild(TestDirective) testDirectiveInstance!: TestDirective;

步骤 3:在父组件的模板中使用 ngTemplateOutlet、ngIf 和 ng-container 投影内容,如下所示:

<div *ngIf="condition">
   <ng-container [ngTemplateOutlet]="testDirectiveInstance.tmpl_ref"></ng-container>
</div>

这里:

  • condition 是用于通过 *ngIf 切换内容投影的实际条件。

  • ngTemplateOutlet 是用于注入和渲染给定模板的内置指令。

  • testDirectiveInstance.tmpl_ref 是对实际模板的引用,该模板包含要投影的操作内容。该模板将使用 ng-template 放置在使用者组件的模板中。我们将在下一步中看到。

  • ng-container 用于确保在内容不会被投影时不会生成不需要的内容/标签。

步骤 4:最后,通过在具有步骤 1 中创建的指令的模板中设置要投影的内容,在任何想要的地方使用该组件。

<ng-template appTestDirective>
   Hi, I am coming from conditional template
</ng-template>

让我们通过更新我们的内容投影示例并更详细地了解条件投影来创建一个可工作的演示。

步骤 1:使用 angular CLI 创建一个指令 greet-content,如下所示:

ng generate directive greet-content
CREATE src/app/greet-content.directive.spec.ts (249 bytes)
CREATE src/app/greet-content.directive.ts (153 bytes)
UPDATE src/app/app.module.ts (795 bytes)

步骤 2:更新指令并获取模板引用,如下所示:

import { Directive, TemplateRef } from '@angular/core';

@Directive({
   selector: '[appGreetContent]'
})
export class GreetContentDirective {
   constructor(public template: TemplateRef<unknown>) { }
}

这里:

  • selector 是识别指令的关键。

  • template 是通过构造函数注入(依赖注入概念)注入到指令中的 TemplateRef 类型引用对象。

步骤 3:更新组件 ContentProjectionSampleComponent 以获取指令对象并设置实际条件,如下所示:

import { Component, ContentChild } from '@angular/core';
import { GreetContentDirective } from '../greet-content.directive';

@Component({
   selector: 'app-content-projection-sample',
   templateUrl: './content-projection-sample.component.html',
   styleUrls: ['./content-projection-sample.component.css']
})
export class ContentProjectionSampleComponent {
   show = true;
   @ContentChild(GreetContentDirective) greet!: GreetContentDirective;
}

这里:

  • show 是一个变量,它保存决定性条件。

  • @ContentChild 是一个装饰器,它将用于获取指令实例。

步骤 4:在组件的模板中,使用 ngIf 检查条件,使用 ng-container 和 ngTemplateOutlet 在组件的模板中显示模板 (greet.template),如下所示:

<p>This is content from the component template</p>
<ng-content></ng-content>
<p>This is another content from component template</p>
<ng-content select="[second]"></ng-content>

<div *ngIf="show">
   <ng-container [ngTemplateOutlet]="greet.template"></ng-container>
</div>

步骤 5:最后,使用该组件及其在 app 组件中的条件投影,如下所示:

<app-content-projection-sample>
   <p>This is external content</p>
   <ng-container ngProjectAs="second">
      <p>This is yet another external content</p>
   </ng-container>
   
   <ng-template appGreetContent>
      Hi, I am coming from conditional template
   </ng-template>
</app-content-projection-sample>

<router-outlet></router-outlet>

步骤 6:运行应用程序并检查输出,以发现内容是通过条件投影概念渲染的。

conditional projection

步骤 7:将组件中的条件 show 更新为 false 并检查输出,以发现 ng-template 内容未渲染。

export class ContentProjectionSampleComponent {
   show = false;
   @ContentChild(GreetContentDirective) greet!: GreetContentDirective;
}
Component Template
广告