您需要了解有关Angular中的ng-template,ng-content,ng-container和* ngTemplateOutlet的所有信息

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_Angular

​普拉泰克·米什拉(Prateek Mishra)​

那是我忙于为Office项目开发新功能的日子之一。突然间,一些事情引起了我的注意:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_Angular_02

最终在Angular中渲染的DOM

在检查DOM时,我看到​​ngcontent​​​Angular将其应用于元素。嗯……如果它们包含最终DOM中的元素,那么有什么用​​<ng-container>​​​?那时我​​<ng-container>​​​和之间感到困惑​​<ng-content>​​。

为了知道我的问题的答案,我发现了概念​​<ng-template>​​​。令我惊讶的是,还有​​*ngTemplateOutlet​​。我开始了寻求两个概念清晰的旅程,但是现在我有了四个,听起来差不多!

您去过这种情况吗?如果是,那么您来对地方了。因此,事不宜迟,让我们一一介绍。

1. <ng-template>

正如其名称所暗示的​​<ng-template>​​​是一个模板元素与结构指示角用途(​​*ngIf​​​,​​*ngFor​​​,​​[ngSwitch]​​和自定义指令)。

这些模板元素仅在存在结构指令的情况下起作用Angular将主机元素(对其应用了指令)包装在内部,​​<ng-template>​​​并​​<ng-template>​​通过用诊断注释替换它来在完成的DOM中使用。

考虑以下简单示例​​*ngIf​​:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_Angular_03

示例1-解释结构指令的角度过程

上面显示的是的角度解释​​*ngIf​​​。Angular将应用指令的主机元素放入其中,​​<ng-template>​​并保持主机原样。最终的DOM与我们在本文开头看到的类似:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_自定义_04

示例1-最终呈现的DOM

用法:

我们已经看到了Angular的用法,​​<ng-template>​​但是如果我们想使用它怎么办?由于这些元素仅适用于结构指令,因此我们可以这样写:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_Angular_05

示例2-使用<ng-template>

这​​home​​​是​​boolean​​​设置为​​true​​value的组件的属性。上面的代码在DOM中的输出:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_用例_06

示例2-最终呈现的DOM

什么都没呈现!:(

但是,即使​​<ng-template>​​正确地使用了结构指令,我们为什么也看不到消息?

这是预期的结果。正如我们已经讨论过的,Angular​​<ng-template>​​用诊断注释替换了。毫无疑问,上面的代码不会产生任何错误,因为Angular可以很好地满足您的用例。您将永远不知道幕后到底发生了什么。

让我们比较一下Angular渲染的以上两个DOM:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_自定义_04

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_用例_06

示例1示例2

如果仔细观察,示例2的最终DOM中会有一个额外的注释标签。Angular解释的代码是:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_Angular_09

示例2的角度解释过程

Angular将您的主机包裹​​<ng-template>​​​在另一个主机中​​<ng-template>​​​,不仅将外部转换​​<ng-template>​​为诊断注释,还转换为内部注释!这就是为什么您看不到任何消息的原因。

要摆脱这种情况,可以通过两种方法获得所需的结果:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_自定义_10

正确使用<ng-template>

方法1:

在这种方法中,您将为Angular提供不需要进一步处理的减糖格式。这次Angular只会转换​​<ng-template>​​​为注释,而使其中的内容保持不变(它们不再​​<ng-template>​​像以前一样位于其中)。因此,它将正确呈现内容。

要了解有关如何将此格式与其他结构性指令一起使用的更多信息,请参考​​本文​​。

方法2:

这是一种看不见的格式,很少使用(使用两个同级​​<ng-template>​​​)。在这里,我们提供一个模板参考​​*ngIf​​​其​​then​​来告诉它应该使用哪个模板,如果条件为真。

​<ng-template>​​​不建议使用这样的倍数(您可以​​<ng-container>​​改用),因为这不是它们的目的。它们用作模板的容器,可以在多个地方重复使用。我们将在本文的后续部分中对此进行详细介绍。

2. <ng-container>

您是否曾经写过或看过类似以下的代码:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_自定义_11

例子1

我们许多人编写此代码的原因是无法在Angular中的单个主机元素上使用多个结构指令。现在,此代码可以正常工作,但是​​<div>​​​如果​​item.id​​它是伪造的值(可能不需要),则会在DOM中引入一些额外的空值。

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_用例_12

示例1-最终呈现的DOM

可能不关心像这样的简单示例,但对于具有复杂DOM(以显示成千上万的数据)的大型应用程序来说,这可能会变得很麻烦,因为元素可能已附加了侦听器,而这些侦听器仍将保留在其中DOM监听事件。

更糟糕的是,应用样式(CSS)必须执行的嵌套级别!

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_Angular_13

图片来自:​​内部反跳​

不用担心,我们​​<ng-container>​​得救!

Angular​​<ng-container>​​是一个不会干扰样式或布局的分组元素,因为Angular不会将其放入DOM中

所以,如果我们写例1用​​<ng-container>​​:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_用例_14

带有<ng-container>的示例1

我们得到的最终DOM为:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_自定义_15

使用<ng-container>最终呈现的DOM

看到我们摆脱了那些空​​<div>​​​的。​​<ng-container>​​当我们只想应用多个结构指令而不在DOM中引入任何额外元素时,就应该使用。

有关更多信息,请参阅​​文档​​。还有另一种用例,用于将模板动态注入页面中。我将在本文的最后一部分介绍这种用例。

3. <ng-content>

它们用于创建可配置的组件。这意味着可以根据其用户的需求来配置组件。这就是众所周知的内容投影。已发布库中使用的组件用于​​<ng-content>​​使其自身可配置。

考虑一个简单的​​<project-content>​​组件:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_自定义_16

示例1- <project-content>定义

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_Angular_17

具有<project-content>组件的内容投影

在​​<project-content>​​组件的开始标记和结束标记内传递的HTML内容是要投影的内容。这就是我们所谓的Content Projection。内容将呈​​<ng-content>​​​现在组件内部。这允许​​<project-content>​​组件的使用者在组件内传递任何自定义页脚,并精确控制他们希望如何呈现它。

多个投影:

如果您可以决定将哪些内容放置在什么地方该怎么办?除了可以将每个内容投影到单个对象中之外​​<ng-content>​​​,还可以使用​​select​​​属性来控制如何投影内容​​<ng-content>​​​。它需要一个元素选择器来决定要在特定对象中投影哪些内容​​<ng-content>​​。

这是如何做:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_Angular_18

示例2-具有更新的<project-content>的多内容投影

我们修改了​​<project-content>​​​定义以执行多内容投影。该​​select​​​属性选择将在特定内部呈现的内容的类型​​<ng-content>​​​。在这里,我们首先​​select​​​要渲染header​​h1​​​元素。如果投影的内容没有​​h1​​​元素,则不会渲染任何内容。同样,第二个​​select​​​寻找一个​​div​​​。其余内容将在最后一个内容中​​<ng-content>​​​以no呈现​​select​​。

调用该组件将如下所示:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_自定义_19

示例2-在父组件中调用<project-content>组件

4. * ngTemplateOutlet

…它们用作模板的容器,可以在多个地方重复使用。我们将在本文的后续部分中对此进行详细介绍。

…还有另一种用例,用于将模板动态注入页面。我将在本文的最后一部分介绍这种用例。

本节将讨论前面提到的以上两点。​​*ngTemplateOutlet​​用于两个场景-将通用模板插入视图的各个部分,而与循环或条件无关,并制作一个高度配置的组件。

模板重用:

考虑一个视图,您必须在其中多个位置插入模板。例如,要放置在网站内的公司徽标。我们可以通过为徽标编写模板一次并在视图中的任何地方重复使用它来实现。

以下是代码段:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_用例_20

示例1-模板重用

如您所见,我们只写了一次徽标模板,并在同一页面上用了一行代码就使用了它三遍!

​*ngTemplateOutlet​​​还接受可以传递以自定义通用模板输出的上下文对象。有关上下文对象的更多信息,请参考官方​​文档​​。

可定制的组件:

第二个用例​​*ngTemplateOutlet​​​是高度定制的组件。考虑我们之前对​​<project-content>​​组件进行一些修改的示例:

示例2-制作可定制的组件project-content.html

以上是的修改版本​​<project-content>​​​,其接受三个输入属性组件- ,  ​​headerTemplate​​​,。​​bodyTemplate​​​ ​​footerTemplate​​​以下是的摘要​​project-content.ts​​:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_用例_21

示例2-制作可定制组件project-content.ts

我们在这里想要实现的是显示从的父组件接收到的页眉,正文和页脚​​<project-content>​​。如果未提供任何一个,我们的组件将在其位置显示默认模板。因此,创建了高度定制的组件。

要使用我们最近修改的组件:

前端知识Angular之ng-template,ng-content,ng-container和* ngTemplateOutlet_自定义_22

示例2-使用新修改的组件<project-content>

这就是我们将模板引用传递给组件的方式。如果未通过其中任何一个,则组件将呈现默认模板。

ng-content vs. * ngTemplateOutlet

它们都可以帮助我们实现高度定制的组件,但是什么时候选择呢?

可以清楚地看到,​​*ngTemplateOutlet​​如果没有提供默认模板,则可以使我们更有能力显示默认模板。

情况并非如此​​ng-content​​​。它按原样呈现内容。您最多可以拆分内容,并借助​​select​​​属性来将其呈现在视图的不同位置。您不能有条件地渲染其中的内容​​ng-content​​。您必须显示从父级收到的内容,而不能根据该内容做出决定。

但是,选择两者之间的选择完全取决于您的用例。至少现在我们的武器​​*ngTemplateOutlet​​​库中有一种新武器,除了​​ng-content​​!的功能外,它还可以更好地控制内容!