前言

Control Flow 是 Angular v17 版本后推出的新模板语法,用来取代 NgIf、NgForOf、NgSwitch 这 3 个 Structure Directive。

Structure Directive 的好处是比较灵活,原理简单,但是即便用了微语法,它看上去还是相当繁琐,而且不够优雅。

Conrol Flow 的好处是它的语法够美,缺点是不必 Structure Directive 灵活,开发者无法做任何 customize,只能看 Angular 给什么用什么。

参考

Docs – Built-in control flow

Docs – Deferrable Views

@if @else if @else

这个是 NgIf 指令的写法

<ng-template #loadingTemplate>
<p>loading...</p>
</ng-template> <p *ngIf="person$ | async as person; else loadingTemplate">{{ person.name }}</p>

这个是 Control Flow 的写法

@if(person$ | async; as person) {
<p>{{ person.name }}</p>
}
@else {
<p>loading...</p>
}

另外,pipe async 后还可以继续判断哦,比如

@if(number$() | async; as number === 5) {
<p>{{ number }}</p>
}

number$ 类型是 Signal<Observable<number>>。

另外,Control Flow 还支持 @else if,这个是 NgIf 指令不支持的。

@if (value >= 5) {
<p>greater than or equal to five</p>
}
@else if (value >= 2) {
<p>greater than or equal to two</p>
}
@else {
<p>less than two</p>
}

Control Flow @if 的原理

Contorl Flow 不依赖 NgIf 指令,甚至不依赖 ViewContainerRef,它使用的是比较底层的接口,

比如 createAndRenderEmbeddedLView 函数和 addLViewToLContainer 函数。这两个函数的源码我们在 Dynamic Component 文章中研究过,

它们也是 ViewContainerRef 内部使用的。

app.component.js

在 renderView 阶段,@if、@else if、@else 分别会创建三个 ng-template。

在 refreshView 阶段,会执行 ɵɵconditional 函数。第二个参数依据判断条件它会宣传第几个 ng-template。

ɵɵconditional 函数的源码在 control_flow.ts

当 @If 遇上 <ng-content select />

为什么要提这个?

the problem...

因为它是一个 Issue – Angular 17 New Control Flow | Allow more than one node at root @if, @else, @for, etc

App Template

<app-test>
@if (true) {
<h1>Hello World</h1>
}
</app-test>

Test Template

<ng-content select="h1"/>

上面这段代码没有任何问题,<ng-content /> 会 select 到 h1 element。

好,我们加点料

<app-test>
@if (true) {
<h1>Hello World</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quod, reiciendis.</p>
}
</app-test>

在 @if 里面加多一行 <p>。

这时会出现一段 warning

意思是 <ng-content /> 无法 select 到 h1。

这个 warning 不是吓唬人的,它是真的就 select 不到 h1 了。

the theory behind の 逛 ng-content 源码

在 ng-content 文章中,我们提到过一个 Angular limitation -- Only first layer can be select

我们逛一段源码了解一下。

App Template

<app-test>
<h1>Hello World</h1>
</app-test>

after compile

表面上看不出任何和 ng-content 相关的地方,但其实在 ɵɵelementStart 函数内它会做一些特别处理。

我们不深入源码,直接看 TView TNode 的结果就好。

Test TNode 和 h1 TNode 代表了

<app-test>
<h1>Hello World</h1>
</app-test>

它们的父子关系也被记入在 TNode 中。

那如果我们加多一个 <p> 呢?

<app-test>
<h1>Hello World</h1>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Modi, aliquid!</p>
</app-test>

结果

child 代表 first child,first child 的 next 是 p。

好,上面是 App Template renderView 后的 TNode 结构。

那我们继续看 Test Template renderView 长啥样。

Test Template

<ng-content select="h1"/>

after compile

ɵɵprojectionDef 函数源码在 projection.ts

到这里就揭秘了。首先它拿 Test TNode.child。第一个传入 Test 组件的 TNode 是 h1。

接着它拿 h1 TNode.next,也就是 p TNode。

整个 for loop 就只是在 first layer child 一直 next 而已,不会去到 second layer。

分别会 loop 到的 TNode 是 <ng-container>,h1,ng-template。

所以 <ng-content> 也只能 select 到这 3 个 TNode,子孙层是 select 不到的。

@if 和 only first layer can be select 的关系

那 @if 和 上面提到的 only first layer can be select 又有什么关系呢?

<app-test>
@if (true) {
<h1>Hello World</h1>
} <h1 *ngIf="true">Hello World</h1>
</app-test>

@if h1 或者 *ngIf h1 compile 后 first layer TNode 会是 h1

所以 <ng-content> 可以 select 到 h1。

但是加入 p 以后就不同了

<app-test>
@if (true) {
<h1>Hello World</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro, fugit!</p>
}
</app-test>

after compile

first layer TNode 不再是 h1 了,所以 <ng-content> 就 select 不到了。

*ngIf 其实也面对同样的问题。

<app-test>
<!-- *ngIf 无法直接加 p -->
<!-- <h1 *ngIf="true">Hello World</h1> --> <!-- 需要 wrap ng-container -->
<ng-container *ngIf="true">
<h1>Hello World</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Porro, fugit!</p>
</ng-container>
</app-test>

wrap 了一层 ng-container,first layer 自然就不可能是 h1 了。

the workaround...

目前没有什么优雅的方案,我们只能强制迎合它的 limitation,硬把 @if 拆成多个。

<app-test>
@if (true) {
<h1>Hello World</h1>
} @if (true) {
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Dicta, maiores?</p>
}
</app-test>

@for @empty

这个是 NgForOf 指令的写法

<ng-template #noDataTemplate>
<p>no datas</p>
</ng-template> <ng-container *ngIf="people.length > 0 else noDataTemplate">
<ng-container *ngFor="let person of people; trackBy: trackByName; let index = index">
<p>index: {{ index }}</p>
<p>name : {{ person.name }}</p>
</ng-container>
</ng-container>

这个是 Control Flow 的写法

@for (person of people; track person.name; let index = $index) {
<p>index: {{ index }}</p>
<p>name : {{ person.name }}</p>
}
@empty {
<p>no datas</p>
}

有 2 个点非常强

  1. trackByFunction 不需要在 AppComponent 里定义了

  2. @empty 取代了 NgIf 指令

@for 的原理我们就不翻源码了,它和 @if 一样都不依赖 Structure Directive,里面都是调用底层的接口,也因为这样 @for 比 NgForOf 指令速度快很多哦。

@switch @case @default

这个是 NgSwitch 指令的写法

<ng-container [ngSwitch]="status">

  <ng-template [ngSwitchCase]="'Completed'">
<p>complete</p>
</ng-template> <ng-template [ngSwitchCase]="'Pending'">
<p>pending</p>
</ng-template> <ng-template ngSwitchDefault>
<p>none</p>
</ng-template> </ng-container>

这个是 Control Flow 的写法

@switch (status)
{
@case ('Completed') {
<p>complete</p>
}
@case ('Pending') {
<p>pending</p>
}
@default {
<p>none</p>
}
}

提醒:@switch 和 ngSwtich 指令一样,都没有 fallthrough 的概念,match 到后会自动 break。

@defer @placeholder @loading @error

@defer 是 Control Flow 独有的,不存在 Defer 指令。

@defer 的作用是延迟显示一段内容,并且内容里包含的组件/指令/Pipe 还会延迟加载。

@defer (on timer(5s)) {
<app-say-hi />
<app-hello-world />
}

上面这段表示 5 秒中后才输出 SayHi 和 HelloWorld 组件。

我们可以这样去理解,@defer 会被 compile 成 ng-template,5 秒钟后会 createEmbeddedView 然后 insert。

此外 compile 时它还会找出涉及的组件,改成使用 import('/path/component.ts') 的方式动态加载,这样可以减少 first load 的 bundle size。

app.component.js

@loading,@placeholder,@error

@defer (on timer(2s)) {
<app-say-hi />
<app-hello-world />
}
@placeholder {
<p>blank...</p>
}
@loading {
<p>loading...</p>
}
@error {
<p>something wrong</p>
}

@defer 的显示流程是这样:

一开始先显示 @placeholder 的内容,等 on timer 触发后,显示 @loading 内容,这时会去 lazyload -> createEmbededView -> insert,最终显示 @defer 内容。

如果 lazyload 的过程失败,那就显示 @error 内容。

minimum 和 after options

有时候 lazyload 或许会太快,loading 一闪而过体验不好,这时可以设置 after 和 minimum。

@loading(after 100ms; minimum 1s) {
<p>loading...</p>
}

当开始 lazyload 后,Angular 会开始计时,超过 100ms 后,如果 lazyload 还没有结束,那就会显示 @loading 内容,

如果 lazyload 在 1 秒钟内完成,它不会马上输出 @defer 内容,而是会等到 minimum 1s 后才输出。

注:after 和 minimum 是同时开始计时的。

@placeholder 也支持 minimum options,不过不支持 after 哦。

Trigger

除了 on timer 以外,还有另外 5 个 Triggers。

  1. @defer (on idle)

    idle 指的是游览器的 requestIdleCallback 事件

  2. @defer (on immediate)

    immediate 表示一旦 Angular first render 结束后立马就触发。

  3. @defer (on timer(500ms))

    timer 就是 setTimeout,可以写 ms (millisecond) 或者 s (second)

  4. @defer (on hover)

    <div #hoverArea>hover area</div>
    @defer (on hover(hoverArea)) {
    <app-say-hi />
    <app-hello-world />
    } @defer(on hover) {
    <app-say-hi />
    <app-hello-world />
    }
    @placeholder {
    <div>hover area</div>
    }

    hover element 默认是 @placeholder 内容,我们可以传入 element 作为指定的 hover area。

  5. @defer(on viewport)

    viewport 指的是当指定的 element 出现在 viewport 里,它是通过 IntersectionObserver 实现的。

    和 on hover 一样,默认的 element 是 @placeholder,我们可以通过传入参数指定 element。

  6. @defer (on interaction)

    interaction 指的是 click 和 keydown 事件,和 on hover / on viewport 一样可以指定 element。

我们可以写 multiple trigger,它的关系是 "or",也就是说只要其中一个 trigger 触发就执行。

@defer (on viewport; on timer(5s)) {
<app-say-hi />
<app-hello-world />
}

当 on viewport 或者超过 5 秒后显示。

Prefetching

prefetching 的作用是多一个 trigger 用于是否提早 lazyload。

@defer (on viewport; prefetch on timer(5s)) {
<app-say-hi />
<app-hello-world />
}
@placeholder {
<p>placeholder</p>
}

5 秒后,虽然 element 没有 on viewport 但会先去加载,只加载不显示,一直到 element on viewport 才显示。

prefetch 也可以写 multiple trigger,只要其中一个触发就执行

@defer (on viewport; on timer(5s); prefetch on timer(3s); prefetch on hover) {
<app-say-hi />
<app-hello-world />
}
@placeholder {
<p>placeholder</p>
}

总结

Control Flow 代码比起 Structure Directive + 微语法整齐很多,代价是无法扩展。

NgIf、NgForOf、NgSwtich 指令相信在不久的将来会被淘汰,但是 ng-template、Structure Directive 和微语法是不会被淘汰的,

而要掌握好 Structure Directive 和微语法,我个人觉得 NgIf、NgForOf、NgSwtich 指令是非常值得参考的。

目录

上一篇 Angular 18+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

下一篇 Angular 18+ 高级教程 – Component 组件 の @let Template Local Variables

想查看目录,请移步 Angular 18+ 高级教程 – 目录

喜欢请点推荐,若发现教程内容以新版脱节请评论通知我。happy coding

Angular 18+ 高级教程 – Component 组件 の Control Flow的更多相关文章

  1. Control Flow 如何处理 Error

    在Package的执行过程中,如果在Data Flow中出现Error,那么Data Flow component能够将错误行输出,只需要在组件的ErrorOutput中进行简单地配置,参考<D ...

  2. vue 基础-->进阶 教程(3):组件嵌套、组件之间的通信、路由机制

    前面的nodejs教程并没有停止更新,因为node项目需要用vue来实现界面部分,所以先插入一个vue教程,以免不会的同学不能很好的完成项目. 本教程,将从零开始,教给大家vue的基础.高级操作.组件 ...

  3. Angular CLI 使用教程指南参考

    Angular CLI 使用教程指南参考 Angular CLI 现在虽然可以正常使用但仍然处于测试阶段. Angular CLI 依赖 Node 4 和 NPM 3 或更高版本. 安装 要安装Ang ...

  4. 一篇文章看懂angularjs component组件

     壹 ❀ 引 我在 angularjs 一篇文章看懂自定义指令directive 一文中详细介绍了directive基本用法与完整属性介绍.directive是个很神奇的存在,你可以不设置templa ...

  5. SSIS的 Data Flow 和 Control Flow

    Control Flow 和 Data Flow,是SSIS Design中主要用到的两个Tab,理解这两个Tab的作用,对设计更高效的package十分重要. 一,Control Flow 在Con ...

  6. 分享25个新鲜出炉的 Photoshop 高级教程

    网络上众多优秀的 Photoshop 实例教程是提高 Photoshop 技能的最佳学习途径.今天,我向大家分享25个新鲜出炉的 Photoshop 高级教程,提高你的设计技巧,制作时尚的图片效果.这 ...

  7. SSIS ->> Control Flow And Data Flow

    In the Control Flow, the task is the smallest unit of work, and a task requires completion (success, ...

  8. [译]Stairway to Integration Services Level 9 - Control Flow Task Errors

    介绍 在本文中,我们会实验 MaximumErrorCount和ForceExecutioResult 故障容差属性,并且还要学习Control Flow task errors, event han ...

  9. Asynchronous JS: Callbacks, Listeners, Control Flow Libs and Promises

    非常好的文章,讲javascript 的异步编程的. ------------------------------------------------------------------------- ...

  10. Chapter 8: Exceptional Control Flow

    概述: 我们可以用一种“流”的概念来理解处理器的工作流程,PC(Program Counter)依次为a0,a1,a2,...,an-1,这个序列可以称作control flow.当然我们并不总是按顺 ...

随机推荐

  1. 搜索Python编程获取相关图书信息

    1.获取相关图书信息 #搜索"Python编程"获取相关图书信息 from selenium import webdriver from selenium.webdriver.su ...

  2. EXPLAIN sql优化方法

    select A . id , A . title , B . title from jos_content   A left join jos_categories B on A . catid = ...

  3. [oeasy]python0132_变量含义_meaning_声明_declaration_赋值_assignment

    变量定义 回忆上次内容 上次回顾了一下历史 python 是如何从无到有的 看到 Guido 长期的坚持和努力   编程语言的基础都是变量声明 python是如何声明变量的呢?   变量 想要定义变量 ...

  4. Python 基于win32com客户端实现Excel操作

    测试环境 Python 3.6.2 代码实现 非多线程场景下使用 新建并保存EXCEL import win32com.client from win32api import RGB def save ...

  5. ElementUI 基于vue+sortable.js实现表格行拖拽

    基于vue+sortable.js实现表格行拖拽 By:授客 QQ:1033553122 实践环境 sortablejs@1.13.0 vue@2.6.11 element-ui@2.13.2 安装s ...

  6. [WPF] 脱机环境实现支持拼音模糊搜索的AutoCompleteBox

    AutoCompleteBox是一个常见的提高输入效率的组件,很多WPF的第三方控件库都提供了这个组件,但基本都是字符串的子串匹配,不支持拼音模糊匹配,例如无法通过输入ldh或liudehua匹配到刘 ...

  7. vue3 + ts 中出现 类型“typeof import(".........../node_modules/vue/dist/vue")”的参数不能赋给类型“Component<any, any, any, ComputedOptions, MethodOptions>”的参数。

    错误示例截图 解决方法 修改shims-vue.d.ts中的内容 declare module "*.vue" { import { defineComponent } from ...

  8. 【Mybatis-Plus】02 Spring整合,基本CRUD

    创建非骨架普通Maven工程: 引入Spring & MybatisPlus的依赖坐标及其它持久层依赖: <properties> <spring.version>5. ...

  9. 关于Isaac Gym的两个版本比较:IsaacGymEnvs/omni.isaac.gym

    原文地址: https://zhuanlan.zhihu.com/p/590468555 重点: IsaacGymEnvs (IGE)和 omni.isaac.gym (OIG)是两个东西. 原文内容 ...

  10. How to evaluate the Messi Hong Kong fraud incident?

    Who is Lionel Messi? URL: https://en.wikipedia.org/wiki/Lionel_Messi As a famous football player, Me ...