Angular 结构型指令原理

Photo by Olivier Collet on Unsplash

声明

本节内容假设您具有基本的组件概念、了解 Angular 内置组件的基本用法以及

结构型指令与属性型指令的区别。

前言

在 Angular 入门系列教程中,已经提到 Angular 的一些内建指令。其中利用

内建的结构型指令,我们可以方便地来控制视图的创建和销毁。本文从 ngIf 源

码入手,带你了解 ngIf 的解析过程、页面视图增删原理,从而掌握动态增删视

图的基本方法。

何为视图

首先让我们来厘清视图这个概念。这对理解后续的章节非常有用。视图其实是

Angular 内置结构体 View 的中文翻译。在应用运行期间,Angular 解析器会

对每个组件中定义的模板(template)进行解析,在这个过程中,它会利用视

图结构体来存放与这个模板相关的 DOM 对象引用,并将组件中与模板相关

的变量与这些对象引用进行绑定(关联)。

这样 ,Angular 就可以将一个非常庞大的页面以组件为单位划分成不同的视

图,并能以视图作为页面操纵的单位,实现页面的创建、销毁和更新。

语法糖还是黑魔法

语法糖和黑魔法都可以形容 Angular 星号前缀这个特殊的内置语法。Angular

老玩家说它是语法糖,因为配合模板解析器它可以实现语法展开,帮助开发者

节约时间,美化代码。初学者也许更愿意称它为黑魔法,因为 * 操作符用在

模板里非常少见,非常容易遗漏。这一节就让我们看看星号前缀展开的内容。

在内建指令中,我们一直使用的是星号前缀与指令名称配合的语法,如*ngIf、

*ngFor 等。作为 Angular 内建模板语法一种形式,星号前缀提示 Angular 模

板解析器(template-parser)对此标签进行特殊‘扩展’。然后由结构型指令与

内置标签配合完成元素的创建与销毁。

可以看看这个从 ngIf 源码中摘得一个例子。第一行对应的是那个熟悉的星号语

法,其最终扩展的语法如第二段代码所示。可以看到,展开后的片段中 ngIf 指

令位于 <ng-template> 标签之中,又回到了那个熟悉的指令的用法!

只不过为什么要引入 <ng-template> 标签?且听我慢慢道来。

内置对象

浏览器通过提供文档对象模型(DOM)为脚本语言控制页面文档提供了极大便

利。在此基础之上,Angular 进一步封装了文档对象模型,我们可以把这些对

象称为内置对象。其中 TemplateRef 和 ViewContainerRef ,分别代表模板对

象和容器对象。通过这两个对象,开发者可以在 Angular 框架下更加安全、方

便地创建和销毁页面文档。

模板对象

TemplateRef 代表了 Angular 中的模板对象,它在 Angular 模板中对应的标

签就是 <ng-template>。模板对象就好比函数,一旦定义了这个代码片,就可

以在视图的任何位置都能进行复用。模板元素本身不会直接在页面上显示,需

要在 Angular 内建 API 或者指令的帮助下才能显示。

容器对象

ViewContainerRef 代表了容器元素对象。在 Angular 中任何元素本身都可以

看作一个容器元素。一个标准的视图操作动作也是在容器中完成的。

动态创建视图!

回到前面那个问题,为什么要引入 <ng-template>?这是因为根据 ngIf 输入条

件的不同,Angular 需要对视图某个位置的片段进行创建和销毁。Angular 对

这个场景进行了抽象。利用容器对象,我们可以控制是要创建还是销毁一个视

图,而具体创建什么视图,由传入容器对象的模板对象来控制。在实际操作

中,我们可以通过 @ViewChild 或者依赖注入的方式获取到所要操作的容器

对象和模板对象。

将模板对象 TemplateRef 传入容器对象的 createEmbbedView 方法,就可以

立马在页面上呈现<ng-template>中的内容啦!

参考链接

ng-template, ng-container, and ng-content in Angular

Working with DOM in Angular: unexpected consequences and optimization techniques

What is the difference between createEmbeddedView and createComponent?

ViewContainerRef 官方文档

发表评论

电子邮件地址不会被公开。 必填项已用*标注