今天当我在做 Angular 开发时,一个知识点引起了我的注意:
在检查 DOM 时,我看到 ngcontent 被 Angular 应用于元素。 嗯……如果它们包含了最终 DOM 中的元素,那么 有什么用? 当时我对 和 的区别感到困惑。
在寻求知道我的问题的答案的过程中,我发现了 的概念。 令我惊讶的是,还有另一个容易混淆的概念: *ngTemplateOutlet。 我开始了我的旅程,寻求对两个概念的澄清,但现在我有四个,听起来几乎一样!
你遇到过这种情况吗? 如果是,那么您来对地方了。 因此,事不宜迟,让我们一一介绍。
1.
顾名思义, 是一个模板元素,Angular 与结构指令(*ngIf、*ngFor、[ngSwitch] 和自定义指令)一起使用。
这些模板元素仅在存在结构指令时才起作用。 Angular 将宿主元素(指令所应用到的元素)包装在 中,并通过用诊断注释(diagnostic comments)替换它来使用完成的 DOM 中的 。
考虑一个简单的 *ngIf 示例:
上面显示的是 *ngIf 的 Angular 解释,也就是解除语法糖之后的实际代码。 Angular 将应用指令的宿主元素放在 中,并保持宿主原样。 最终的 DOM 类似于我们在本文开头看到的:
2.
我们很多人编写这段代码的原因是无法在 Angular 中的单个宿主元素上使用多个结构指令。 现在这段代码工作正常,但如果 item.id 是一个可能不需要的虚假值,它会在 DOM 中引入几个额外的空
。
人们可能不会关心像这样的简单示例,但是对于具有复杂 DOM(显示数万个数据)的大型应用程序,这可能会变得很麻烦,因为元素可能具有附加到它们的侦听器,这些侦听器仍然存在于 DOM 监听事件。
更糟糕的是应用样式 (CSS) 必须执行的嵌套级别!
不用担心,我们有 来救援!
Angular 是一个不会干扰样式或布局的分组元素,因为 Angular 不会将它放在 DOM 中。
使用 ng-container 重写。
可以理解成把 div 标签放置到 ng-container 这个虚无的容器里,当 div 的 *ngIf 指令布尔值为 false 时,虚无的容器连同里面的 div 标签压根就不会生成。
最后渲染出的 HTML 代码里,没有多余的空 div 标签了:
最佳实践:当我们只想应用多个结构指令而不在我们的 DOM 中引入任何额外元素时,我们应该使用 。
3.
它们用于创建可配置组件。 这意味着可以根据用户的需要配置组件。 这就是众所周知的内容投影: Content Projection. 已发布库中使用的组件使用 使自己可配置。
考虑一个简单的 组件,下图是其本身的 HTML 定义:
显然,footer 区域允许动态配置内容。
下图展示了如何为 footer 区域动态注入自定义 footer 内容。这种用法称为单一投射。
组件的开始和结束标记中传递的 HTML 内容就是要投影的内容。 这就是我们所说的内容投影。 内容将在提供内容投影功能组件内的 内呈现。
这允许 组件的使用者在组件内传递任何自定义页脚并准确控制他们希望如何呈现它。
Multiple Projections
如果您可以决定哪些内容应该放在什么地方呢? 除了将每个内容投影到单个 中之外,您还可以使用 的 select 属性控制内容的投影方式。 它需要一个元素选择器来决定在特定的 中投射哪些内容。
就是这样:
我们修改了 定义以执行多内容投影。 select 属性选择将在特定 中呈现的内容类型。 这里我们首先选择渲染标题 h1 元素。 如果投影内容没有 h1 元素,它将不会呈现任何内容。 同样,第二个选择查找 div。 其余内容在最后一个 中呈现,没有选择。
如何消费这个带有 select 属性的,允许多重投射的组件?方法如下所示:
4. *ngTemplateOutlet
*ngTemplateOutlet 用于两种场景:
在视图的各个部分插入一个通用模板,而不考虑循环或条件
制作高度配置的组件。
模板重用
考虑一个视图,您必须在多个位置插入模板。 例如,要放置在网站中的公司徽标。 我们可以通过为徽标编写一次模板并在视图中的任何地方重用它来实现它。
以下是代码片段:
如您所见,我们只编写了一次徽标模板,并在同一页面上使用一行代码将其使用了 3 次!
Customizable components
*ngTemplateOutlet 的第二个用例是高度定制的组件。 考虑我们之前的 组件示例,并进行了一些修改:
以上是 组件的修改版本,它接受三个输入属性 —— headerTemplate、bodyTemplate、footerTemplate。 以下是 project-content.ts 的片段:
模板文件里使用到的 input 属性,headerTemplate,bodyTemplate 和 footerTemplate 属性定义在 Component 文件里。
我们在这里试图实现的是显示从 的父组件接收到的页眉、正文和页脚。 如果未提供其中任何一个,我们的组件将在其位置显示默认模板。 因此,创建了一个高度定制的组件。
要使用我们最近修改的组件:
这就是我们将模板引用传递给我们的组件的方式。 如果其中任何一个未通过,则组件将呈现默认模板。
ng-content vs. *ngTemplateOutlet
它们都可以帮助我们实现高度定制化的组件,但选择哪个以及何时选择?
可以清楚地看到,如果没有提供, *ngTemplateOutlet 为我们提供了更多显示默认模板的能力。
这不是 ng-content 的情况。 它按原样呈现内容。 您最多可以在 select 属性的帮助下拆分内容并在视图的不同位置呈现它们。 您不能有条件地呈现 ng-content 中的内容。 您必须显示从父级收到的内容,而无法根据内容做出决定。
但是,在两者中进行选择完全取决于您的用例。 至少现在我们的武器库中有一个新武器 *ngTemplateOutlet,除了 ng-content 的功能外,它还提供了对内容的更多控制!