简介
单一责任原则是指应用程序的各个部分应该有一个目的。遵循这个原则可以让你的Angular应用更容易测试和开发。
在角度上,使用NgTemplateOutlet
而不是创建特定的组件,允许针对各种用例轻松修改组件,而不必修改组件本身!
在本文中,您将使用一个已有的组件并将其重写为使用NgTemplateOutlet
。
前提条件
要完成本教程,您需要:
- 本地安装Node.js,可以按照如何安装Node.js并创建本地开发环境操作。
- 熟悉设置Angular项目。
本教程已经在Node v16.6.2、npm
v7.20.6和@angular/core
v12.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}
该组件不具有单一职责,并且灵活性不高。它需要跟踪它的模式
,并知道如何在card
和list
视图中显示items
。并且只能显示带有Header
和Content
的items
。
让我们通过使用模板将组件分解为单独的视图来改变这一点。
第二步-了解ng-template
和NgTemplateOutlet
为了允许CardOrListViewComponent
显示任何类型的Items
,我们需要能够告诉它如何显示它们。我们可以通过为它提供一个模板来实现这一点,它可以使用该模板来标记`items‘。
模板将是使用<ng-模板>
的TemplateRefs
,邮票将是由TemplateRefs
创建的EmbeddedViewRefs
。EmbeddedViewRefs
表示具有自身上下文的角度视图,是最小的基本构建块。
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>
default
和ther
变量由let-default
和let-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
将导入CardItemDirective
和ListItemDirective
:
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
的责任更小。我们可以在条目上下文中添加更多内容,比如类似于ngFor
的first
或last
,或者显示完全不同类型的items
。
结论
在本文中,您获取了一个已有组件并将其重写为使用NgTemplateOutlet
。
如果您想了解更多有关ANGLE的信息,请查看我们的ANGLE主题页面以获取练习和编程项目。