Angular:使用 ngTemplateOutlet 构建更多动态组件🎭
介绍
定义
用例 #1:上下文感知模板
用例 #2:模板重载
用例 #3:树
总结
介绍
为了构建可复用且开发者友好的组件,我们需要使其更具动态性(即更具适应性)。好消息是,Angular 提供了一些很棒的工具来实现这一点。例如,我们可以使用以下命令将内容注入到组件中<ng-content>
:
@Component({
selector: 'child-component',
template: `
<div class="child-component">
<ng-content></ng-content>
</div>
`,
})
export class ChildComponent {}
@Component({
selector: 'parent-component',
template: `
<child-component>
Transcluded content
</child-component>
`,
})
export class ParentComponent {}
虽然这种嵌入技术对于简单的内容投影来说非常有效,但如果您希望投影的内容具有上下文感知能力,该怎么办呢?例如,在实现列表组件时,您希望在父组件中定义项目模板,并使其具有上下文感知能力(即当前托管的项目是什么)。
对于这类场景,Angular 提供了一个非常棒的 API,叫做ngTemplateOutlet
!
在本文中,我们将定义什么ngTemplateOutlet
是 Angular,然后构建上面提到的列表组件以及卡片组件,以了解两个最常见的ngTemplateOutlet
用例。我们将逐步实现这些组件,因此在本文结束时,您应该能够轻松地在 Angular 组件中使用这些组件 :)
定义
从当前的 Angular 文档中ngTemplateOutlet
可以得知一条指令:从准备好的 TemplateRef 插入嵌入视图。
该指令有两个属性:
- ngTemplateOutlet:模板引用(类型:)
TemplateRef
。 $implicit
ngTemplateOutletContext:要附加到 EmbeddedViewRef 的上下文对象。使用上下文对象中的键将把其值设置为默认值。
这意味着,在子组件中,我们可以从父组件获取一个模板,并将一个上下文对象注入到这个模板中。然后,我们就可以在父组件中使用这个上下文对象了。
如果您觉得这太抽象,这里有一个如何使用它的例子:
<!-- Child component -->
<child-component>
<ng-container
[ngTemplateOutlet]="templateRefFromParentComponent"
[ngTemplateOutletContext]="{ $implicit: 'Joe', age: 42 }"
>
</ng-container>
</child-component>
<!-- Parent component -->
<parent-component [templateRefFromParentComponent]="someTemplate">
<ng-template #someTemplate let-name let-age="age">
<p>{{ name }} - {{ age }}</p>
</ng-template>
</parent-component>
在上面的代码中,子组件将包含一个包含“Joe - 42”的段落。
请注意,对于 name ( let-name
),我们没有指定要使用上下文对象的哪个属性,因为 name 存储在该$implicit
属性中。另一方面,对于 age ( let-age="age"
),我们指定了要使用的属性名称(在本例中为age
)。
好了,定义就这么多。让我们开始写代码吧。
本文中展示的代码可以在 Github 仓库中找到
用例 #1:上下文感知模板
让我们构建一个从其父级获取两个输入的列表组件:
- 数据:对象列表。
- itemTemplate:用于表示列表的每个元素的模板。
运行
ng new templateOutletTutorial --minimal
生成一个小型 Angular 项目来编写代码
让我们使用 Angular 原理图 ( ) 生成列表组件ng g c components/list
。完成后,让我们实现该组件,它将显示 data 属性(输入列表)的每个项目。在 的每次迭代中ng-for
,它都会在 itemTemplate 属性中插入父组件提供的嵌入视图。执行此操作时,组件应附加一个包含当前项目的上下文对象。
最终,列表组件应如下所示:
@Component({
selector: 'app-list',
template: `
<ul class="list">
<li class="list-item" *ngFor="let item of data">
<ng-container
[ngTemplateOutlet]="itemTemplate"
[ngTemplateOutletContext]="{ $implicit: item }"
></ng-container>
</li>
</ul>
`,
styleUrls: ['list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ListComponent {
@Input() data: any[];
@Input() itemTemplate: TemplateRef<HTMLElement>; // a template reference of a HTML element
}
然后在父组件中,我们需要使用列表(对象)和模板引用来调用列表组件:
<app-list
[itemTemplate]="customItemTemplate"
[data]="[{ id: 4, name: 'Laptop', rating: 3 },
{ id: 5, name: 'Phone', rating: 4 },
{ id: 6, name: 'Mice', rating: 4 }]"
>
<ng-template #customItemTemplate let-item>
<div style="display: flex; justify-content: space-between;">
<span> {{ item.id }} - <b>{{ item.name }}</b> </span>
<mark> Stars: {{ item.rating }} </mark>
</div>
</ng-template>
</app-list>
请注意,我们将 ng-template(项目模板)放在了 app-list 组件标签内。这只是为了方便阅读,您可以将项目模板放在父模板中的任何位置。
此外,我在项目模板中添加了一些内联样式,但您也可以为其添加一个类名,并在父组件样式文件中设置样式。
用例 #2:模板重载
我们了解了如何ngTemplateOutlet
帮助我们投射上下文感知模板,让我们看看另一个很好的用例:模板重载。
为此,我们将构建一个由两部分组成的卡片组件:
- 标题:卡片的标题。
- 内容:卡片的主要内容。
对于标题,我们将传递一个简单的字符串,对于内容,我们可以使用内容投影来注入它。在使用 Angular 原理图 ( ng g c components/card
) 创建卡片组件后,我们就可以这样做了,该组件应如下所示:
@Component({
selector: 'app-card',
template: `
<div class="card">
<header>{{ title }}</header>
<article>
<ng-content></ng-content>
</article>
</div>
`,
styleUrls: ['card.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CardComponent {
@Input() title: string;
}
我们在父组件模板中调用它:
<app-card [title]="'hello there'">
<p>i'm an awesome card.</p>
</app-card>
现在假设我们想<img>
在标题中放置一张图片(),或者在标题模板中使用其他组件。由于 title 属性只能接受字符串,我们可能会遇到困难。
为了解决这个问题,我们可以在卡片组件中实现一个新的行为。我们可以将标题定义为字符串或 TemplateRef。如果是字符串,我们将使用字符串插值将其绑定到模板;否则,我们将使用ngTemplateOutlet
。
实施这些更改后,新的卡片组件应如下所示:
@Component({
selector: 'app-card',
template: `
<div class="card">
<header *ngIf="isTitleAString(); else titleTemplateWrapper">{{ title }}</header>
<ng-template #titleTemplateWrapper>
<ng-container [ngTemplateOutlet]="title"></ng-container>
</ng-template>
<article>
<ng-content></ng-content>
</article>
</div>
`,
styleUrls: ['card.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CardComponent {
@Input() title: string | TemplateRef<HTMLElement>;
isTitleAString = () => typeof this.title == 'string';
}
我们在父组件模板中这样调用它:
<app-card [title]="title">
<ng-template #title> <h2>Hello there</h2> </ng-template>
<p>i'm an awesome card.</p>
</app-card>
用例 #3:树
总结
好了,我们了解了什么ngTemplateOutlet
是 TensorFlow,以及如何利用它。我们了解了 3 个最常见的用例,但现在您已经了解了这项技术,也许您还能发现另一个很棒的用例!
这篇文章就到这里。希望你喜欢。如果喜欢,请分享给你的朋友和同事。你也可以在 Twitter 上关注我@theAngularGuy,这会对我有很大帮助。
祝你有美好的一天 !