Angular 控制流与延迟视图揭秘

2023年11月8日 Angular 团队发布了 Angular 17 开发预览版,在新的版本,Angular 添加了许多激动人心的特性,其中就包含新的控制流和延迟视图

新版 Angular 增加一个 Block的概念,

Block是模板中的一种新语法结构,控制流和延迟视图也是基于这种语法结构来实现的。

控制流

什么是控制流?

控制流是指程序中决定语句执行顺序的机制。它通过顺序、选择(条件判断)、循环等结构,使程序能够根据不同条件或规则执行不同的代码块,实现灵活的逻辑控制,代码中常见的 if-else  for 都属于控制流语法。

Angular 中的控制流

Angular 16 及之前的版本中的控制流是基于微语法和结构指令来实现的,比如:

html 复制代码
<div *ngIf="condition; else elseBlock">
  Content to render when the condition is true.
</div>
<ng-template #elseBlock>
   Content to render when the condition is false.
</ng-template>

html 复制代码
<ng-container [ngSwitch]="tab">
  <basic-info *ngSwitchCase="'basic'"></basic-info>
  <attachment-list *ngSwitchCase="'attachment'"></attachment-list>
  <version-list *ngSwitchCase="'version'"></version-list>
  <invaild-tab *ngSwitchDefault></invaild-tab>
</ng-container>
html 复制代码
<ng-container [ngSwitch]="tab">
  <ng-container *ngSwitchCase="'version'">
    <version-list></version-list>
  </ng-container>
  <ng-container *ngSwitchCase="'attachment'"> <attachment-list></attachment-list> </ng-container>
  <ng-container *ngSwitchCase="'basic'">
    <basic-info></basic-info>
  </ng-container>
  <ng-container *ngSwitchDefault>
    <invaild-tab></invaild-tab>
  </ng-container>
</ng-container>


开发体验不好:

  • 不够直观简洁
  • 灵活度差
  • 微语法模型也不支持控制流语句的多个关联子模板,所以 Angular 无法解决 *ngIf 的情况 else 使用起来非常尴尬的问题
  • 独立组件需要引入

新的控制流

关注 Angular 的同学了解,Angular 一直在推动 Zoneless 和 Singals 的工作,Angular 中现有的基于 Zone 的控制流指令将无法在 Zoneless 的应用中运行的,在考虑修改现有指令以支持 Zoneless 应用时,Angular 团队决定不这样做,因为潜在的重大更改(需要兼容旧的应用程序)和代码复杂性增加。相反,他们选择引入一种新的内置控制流语法,该语法既支持无区域应用程序,又解决微语法长期存在的 DX(开发体验)问题。

语法

官方在 RFC 时候提出了 #-syntax 类似 HTML 的标签语法,如 {#if} 、 {:else} 和 {/if}

html 复制代码
{#if cond.expr}
  Main case was true!
{:else if other.expr}
  Extra case was true!
{:else}
  False case!
{/if}


不过在 RFC 讨论阶段,大部分人更喜欢 @-syntax 的语法,最终 Angular 团队通过调查和研究发现,大部分人也都认可这个方案,最终确定了使用了 @-syntax 的语法

html 复制代码
@if (cond.expr){
   Main case was true!
} @else {
   False case!
}


使用

IF-ELSE

html 复制代码
<div class="status-label" *ngIf="status === 1; else invalid">正常</div>
<div class="status-label" *ngIf="status === 2; else invalid">进行中</div>
<div class="status-label" *ngIf="status === 3; else invalid">完成</div>

<ng-template #invalid>
  <div class="status-label">无效状态</div>
</ng-template>

html 复制代码
<div class="status-label">
  @if (status === 1) {
    正常
  } @else if (status === 2) {
    进行中
  } @else if (status === 3) {
    完成
  } @else {
    无效状态
  }
</div>

@if (users$ | async; as users) {
    {{ users.length }}
}


Switch

html 复制代码
<ng-container [ngSwitch]="tab">
  <basic-info *ngSwitchCase="'basic'"></basic-info>
  <attachment-list *ngSwitchCase="'attachment'"></attachment-list>
  <version-list *ngSwitchCase="'version'"></version-list>
  <invaild-tab *ngSwitchDefault></invaild-tab>
</ng-container>

For Loop

默认的 trackBy

@Component({

template: `

  • <ng-container *ngIf="users?.length > 0; else empty">
    <li *ngFor="let user of users; trackBy: trackFn; let i = index">
    No.${{ i + 1 }} {{ user.name }}

    <ng-template #empty>没有用户</ng-template>

`,

})

export class DemoComponent {

trackFn(index: number, item: Item) {

return item.id;

}

}

  • @for (user of users; track user.id; let i = $index, e = $even) {
  • No.${{ i + 1 }} {{ user.name }}
  • } @empty { 没有用户 }

  • @for (user of (users$ | async); track user.id; let i = $index, e = $even) {
  • No.${{ i + 1 }} {{ user.name }}
  • }

延迟视图

在 Angular 16 中如何实现延迟加载组件?

@Component({

template: <ng-container #viewContainer />,

})

export class OldLazyComponent implements AfterViewInit {

@ViewChild('viewContainer', { read: ViewContainerRef })

viewContainerRef!: ViewContainerRef;

async ngAfterViewInit() {

const { DemoComponent } = await import('./demo/demo.component');

const componentRef = this.viewContainerRef.createComponent(DemoComponent);

}

}

@defer

Angular 支持通过 Router 延迟加载应用程序的某些部分(延迟路由),单个组件的延迟加载可以通过 dynamic import() 和 ngComponentOutlet 实现,但这种方法可能很复杂且容易出错,因此 Angular 17 在核心框架中引入一种更符合人体工程学的延迟加载组件的方法 @defer , @defer 不仅仅支持延迟组件,同时也支持延迟加载指令和管道,随着新的 @defer 的引入,我们可以更细颗粒度的控制我们的加载资源,优化应用初始包的体积,提升用户加载的速度。

使用限制

@defer 块中的组件必须是独立组件,非独立组件不支持延迟加载并且会立即加载,及时他被包裹在 @defer 块中

不能在 @defer 块以外引用这个组件,包括使用 @ViewChild 查询这个组件

使用

@defer

@defer 块中的内容最初不会显示,当满足指定的触发器或条件并获取依赖项,块中的内容才会展示,默认情况下,当浏览器状态变为空闲状态(Idle)时,会触发 @defer 加载。

@defer {

}

@defer {

{{name | transterName}}

}

@placeholder

默认情况下, @defer 块在触发之前不会呈现任何内容,我们可以定义 @placeholder 可选的块,声明在触发延迟块之前要显示的内容,当延迟内容加载完成后, @placeholder 块中的内容会销毁,需要注意的是在 @placeholder 块中的内容永远都是立即记载的

@defer {

} @placeholder {

}

@placeholder 块接受一个可选参数 minimum 来指定应显示此占位符的时间,单位支持 s 和 ms。 minimum 是为了防止延迟项加载过快导致内容闪烁。

@defer {

} @placeholder(minimum 500ms) {

}

@loading

@loading 与 @placeholder 一样,也是是一个可选块,允许我们指定加载依赖期间需要展示的内容,与 @placeholder 类似,它也是立即加载的,它可以接收两个参数 after 和 minimum

@defer {

}@loading((after 100ms; minimum 1s)) {

}

@error

@error 比较好理解,我们可以在 @error 中指定依赖加载失败时候展示的内容,与 @placeholder 和 @loading , @error 块的内容也是立即加载的,并且也是可选的

@defer {

}@error{
组件加载错误
}  触发器 Triggers 默认情况下,当浏览器状态变为空闲状态(Idle)时,会触发 @defer 加载,不过我们也可以根据自己的需求指定其他的触发器,Angular 提供了两种触发方式  When 和  On  When 条件触发 指定一个条件,当满足这个条件时触发   @defer (when cond) { }  On on 支持指定以下几种内置的触发器  Tigger Desc Example on idle 浏览器闲时触发,利用浏览器的 requestIdleCallback 特性   @defer (on idle) { }  on immediate 应用渲染完后立即触发   @defer (on immediate) { }  on timer 指定一个时间间隔后触发   @defer (on timer(500ms)) { }  on viewport Placeholder 或指定元素进入可视区域后触发,利用的 IntersectionObserver 特性,组员保证 Placeholder 必须是一个DOM节点   @defer (on viewport) { }@placeholder {
Placeholder
}

const componentFixture = TestBed.createComponent(ComponentA);

// Retrieve the list of all defer block fixtures and get the first block.

const deferBlockFixture = componentFixture.getDeferBlocks()[0];

const deferBlockFixture = componentFixture.getDeferBlocks()[0];

// Render loading state and verify rendered output.

await deferBlockFixture.render(DeferBlockState.Loading);

expect(componentFixture.nativeElement.innerHTML).toContain('Loading');

expect(componentFixture.nativeElement.innerHTML).toContain('加载');

// Render final state and verify the output.

await deferBlockFixture.render(DeferBlockState.Completed);

expect(componentFixture.nativeElement.innerHTML).toContain('');

});

插件支持

Prettier

npm i prettier@3.1 --save-dev

QA

支持自定义 Block 吗?

@defer 可以定义自己的 on 的触发器吗?

控制流会支持自动迁移吗?

现有结构指令会废弃吗?

新的控制流会不会影响 ViewChild 查询结果?

新的控制流有更好的性能?

CDK 的虚拟滚动会收到影响吗?

CDK 目前将继续为虚拟滚动和其他用例提供其现有的结构指令。Angular 将研究将 CDK 的一些结构指令转换为内置语法,或者将扩展点添加到现有语法中供 CDK 构建(例如,支持虚拟 for 滚动)

相关推荐
小王码农记2 小时前
vue中路由缓存
前端·vue.js·缓存·typescript·anti-design-vue
西电研梦6 小时前
考研倒计时30天丨和西电一起向前!再向前!
人工智能·考研·1024程序员节·西电·西安电子科技大学
惜.己7 小时前
Jmeter中的断言(四)
测试工具·jmeter·1024程序员节
·云扬·15 小时前
Java IO 与 BIO、NIO、AIO 详解
java·开发语言·笔记·学习·nio·1024程序员节
网安_秋刀鱼18 小时前
PHP代码审计 --MVC模型开发框架&rce示例
开发语言·web安全·网络安全·php·mvc·1024程序员节
HUODUNYUN1 天前
小程序免备案:快速部署与优化的全攻略
服务器·网络·web安全·小程序·1024程序员节
Star7681 天前
ts泛型的一个小知识
前端·typescript
惜.己1 天前
Jmeter的后置处理器(二)
测试工具·github·1024程序员节
惜.己2 天前
Jmeter中的断言(一)
测试工具·jmeter·1024程序员节
盛夏绽放2 天前
Vue 3与TypeScript集成指南:构建类型安全的前端应用
前端·vue.js·typescript