如何在 Angular 中使用 NgTemplateOutlet 创建可重用组件

简介

单一责任原则是指应用程序的各个部分应该有一个目的。遵循这个原则可以让你的Angular应用更容易测试和开发。

在角度上,使用NgTemplateOutlet而不是创建特定的组件,允许针对各种用例轻松修改组件,而不必修改组件本身!

在本文中,您将使用一个已有的组件并将其重写为使用NgTemplateOutlet

前提条件

要完成本教程,您需要:

本教程已经在Node v16.6.2、npmv7.20.6和@angular/corev12.2.0上进行了验证。

Step 1 -构造CardOrListViewComponent

CardOrListViewComponent为例,它根据其模式‘卡片’‘列表’格式显示

它由一个Card-or-list-view.Component.ts文件组成:

 1[label card-or-list-view.component.ts]
 2import {
 3  Component,
 4  Input
 5} from '@angular/core';
 6
 7@Component({
 8  selector: 'card-or-list-view',
 9  templateUrl: './card-or-list-view.component.html'
10})
11export class CardOrListViewComponent {
12
13  @Input() items: {
14    header: string,
15    content: string
16  }[] = [];
17
18  @Input() mode: string = 'card';
19
20}

以及Card-or-list-view.Component.html模板:

 1[label card-or-list-view.component.html]
 2<ng-container [ngSwitch]="mode">
 3  <ng-container *ngSwitchCase="'card'">
 4    <div *ngFor="let item of items">
 5      <h1>{{item.header}}</h1>
 6      <p>{{item.content}}</p>
 7    </div>
 8  </ng-container>
 9  <ul *ngSwitchCase="'list'">
10    <li *ngFor="let item of items">
11      {{item.header}}: {{item.content}}
12    </li>
13  </ul>
14</ng-container>

以下是此组件的用法示例:

 1[label usage.component.ts]
 2import { Component } from '@angular/core';
 3
 4@Component({
 5  template: `
 6    <card-or-list-view
 7        [items]="items"
 8        [mode]="mode">
 9    </card-or-list-view>
10`
11})
12export class UsageExample {
13  mode = 'list';
14  items = [
15    {
16      header: 'Creating Reuseable Components with NgTemplateOutlet in Angular',
17      content: 'The single responsibility principle...'
18    } // ... more items
19  ];
20}

该组件不具有单一职责,并且灵活性不高。它需要跟踪它的模式,并知道如何在cardlist视图中显示items。并且只能显示带有HeaderContentitems

让我们通过使用模板将组件分解为单独的视图来改变这一点。

第二步-了解ng-templateNgTemplateOutlet

为了允许CardOrListViewComponent显示任何类型的Items,我们需要能够告诉它如何显示它们。我们可以通过为它提供一个模板来实现这一点,它可以使用该模板来标记`items‘。

模板将是使用<ng-模板>TemplateRefs,邮票将是由TemplateRefs创建的EmbeddedViewRefsEmbeddedViewRefs表示具有自身上下文的角度视图,是最小的基本构建块。

Angel提供了一种使用NgTemplateOutlet从模板中打印视图的概念。

NgTemplateOutlet是一个指令,它接受TemplateRef和上下文,并用提供的上下文标记出EmbeddedViewRef。通过let-{{templateVariableName}}=contextProperty``属性在模板上访问上下文,以创建模板可以使用的变量。如果没有提供上下文属性名称,它将选择$implicit属性。

下面是一个例子:

 1import { Component } from '@angular/core';
 2
 3@Component({
 4  template: `
 5    <ng-container *ngTemplateOutlet="templateRef; context: exampleContext"></ng-container>
 6    <ng-template #templateRef let-default let-other="aContextProperty">
 7      <div>
 8        $implicit = '{{default}}'
 9        aContextProperty = '{{other}}'
10      </div>
11    </ng-template>
12`
13})
14export class NgTemplateOutletExample {
15  exampleContext = {
16    $implicit: 'default context property when none specified',
17    aContextProperty: 'a context property'
18  };
19}

以下是该示例的输出:

1<div>
2  $implicit = 'default context property when none specified'
3  aContextProperty = 'a context property'
4</div>

defaultther变量由let-defaultlet-ther=aConextProperty``道具提供。

第三步-重构CardOrListViewComponent

为了给CardOrListViewComponent提供灵活性,并允许它显示任何类型的items,我们将创建两个结构性指令作为模板读入。这些模板将成为卡片和列表项。

这里是Card-item.Directive.ts

 1[label card-item.directive.ts]
 2import { Directive } from '@angular/core';
 3
 4@Directive({
 5  selector: '[cardItem]'
 6})
 7export class CardItemDirective {
 8
 9  constructor() { }
10
11}

这里是list-item.Directive.ts

 1[label list-item.directive.ts]
 2import { Directive } from '@angular/core';
 3
 4@Directive({
 5  selector: '[listItem]'
 6})
 7export class ListItemDirective {
 8
 9  constructor() { }
10
11}

CardOrListViewComponent将导入CardItemDirectiveListItemDirective

 1[label card-or-list-view.component.ts]
 2import {
 3  Component,
 4  ContentChild,
 5  Input,
 6  TemplateRef 
 7} from '@angular/core';
 8import { CardItemDirective } from './card-item.directive';
 9import { ListItemDirective } from './list-item.directive';
10
11@Component({
12  selector: 'card-or-list-view',
13  templateUrl: './card-or-list-view.component.html'
14})
15export class CardOrListViewComponent {
16
17  @Input() items: {
18    header: string,
19    content: string
20  }[] = [];
21
22  @Input() mode: string = 'card';
23
24  @ContentChild(CardItemDirective, {read: TemplateRef}) cardItemTemplate: any;
25  @ContentChild(ListItemDirective, {read: TemplateRef}) listItemTemplate: any;
26
27}

这段代码将我们的结构指令读入为TemplateRefs

 1[label card-or-list-view.component.html]
 2<ng-container [ngSwitch]="mode">
 3  <ng-container *ngSwitchCase="'card'">
 4    <ng-container *ngFor="let item of items">
 5      <ng-container *ngTemplateOutlet="cardItemTemplate"></ng-container>
 6    </ng-container>
 7  </ng-container>
 8  <ul *ngSwitchCase="'list'">
 9    <li *ngFor="let item of items">
10      <ng-container *ngTemplateOutlet="listItemTemplate"></ng-container>
11    </li>
12  </ul>
13</ng-container>

以下是此组件的用法示例:

 1[label usage.component.ts]
 2import { Component } from '@angular/core';
 3
 4@Component({
 5  template: `
 6    <card-or-list-view
 7        [items]="items"
 8        [mode]="mode">
 9      <div *cardItem>
10        Static Card Template
11      </div>
12      <li *listItem>
13        Static List Template
14      </li>
15    </card-or-list-view>
16`
17})
18export class UsageExample {
19  mode = 'list';
20  items = [
21    {
22      header: 'Creating Reuseable Components with NgTemplateOutlet in Angular',
23      content: 'The single responsibility principle...'
24    } // ... more items
25  ];
26}

有了这些更改,CardOrListViewComponent现在可以根据提供的模板在卡片或列表表单中显示任何类型的项目。目前,模板是静态的。

我们需要做的最后一件事是通过为模板提供上下文来允许它们是动态的:

 1[label card-or-list-view.component.html]
 2<ng-container [ngSwitch]="mode">
 3  <ng-container *ngSwitchCase="'card'">
 4    <ng-container *ngFor="let item of items">
 5      <ng-container *ngTemplateOutlet="cardItemTemplate; context: {$implicit: item}"></ng-container>
 6    </ng-container>
 7  </ng-container>
 8  <ul *ngSwitchCase="'list'">
 9    <li *ngFor="let item of items">
10      <ng-container *ngTemplateOutlet="listItemTemplate; context: {$implicit: item}"></ng-container>
11    </li>
12  </ul>
13</ng-container>

以下是此组件的用法示例:

 1[label usage.component.ts]
 2import { Component } from '@angular/core';
 3
 4@Component({
 5  template: `
 6    <card-or-list-view
 7        [items]="items"
 8        [mode]="mode">
 9      <div *cardItem="let item">
10        <h1>{{item.header}}</h1>
11        <p>{{item.content}}</p>
12      </div>
13      <li *listItem="let item">
14        {{item.header}}: {{item.content}}
15      </li>
16    </card-or-list-view>
17`
18})
19export class UsageExample {
20  mode = 'list';
21  items = [
22    {
23      header: 'Creating Reuseable Components with NgTemplateOutlet in Angular',
24      content: 'The single responsibility principle...'
25    } // ... more items
26  ];
27}

有趣的是,我们使用星号前缀和microsyntax来表示语法上的糖。它与以下内容相同:

1<ng-template cardItem let-item>
2  <div>
3    <h1>{{item.header}}</h1>
4    <p>{{item.content}}</p>
5  </div>
6</ng-template>

就是这样!我们有原来的功能,但现在我们可以通过修改模板来显示我们想要的任何东西,CardOrListViewComponent的责任更小。我们可以在条目上下文中添加更多内容,比如类似于ngForfirstlast,或者显示完全不同类型的items

结论

在本文中,您获取了一个已有组件并将其重写为使用NgTemplateOutlet

如果您想了解更多有关ANGLE的信息,请查看我们的ANGLE主题页面以获取练习和编程项目。

Published At
Categories with 技术
Tagged with
comments powered by Disqus