过去几年对 Angular 来说是变革性的,我们推出了如 Signals 响应式和 Zoneless 应用等重大进步。我们希望这些特性能帮助 Angular 社区构建新一代 Web 应用,实现更快的上市时间和强大的性能。
而这仅仅是个开始!Angular v20 是我们最新的版本,我们花费了无数时间打磨正在开发中的特性,只为给你带来坚如磐石的开发体验。
本次更新亮点:
- 稳定了
effect
、linkedSignal
、toSignal
等 API,增量水合、路由级渲染模式配置,并将 zoneless 推进到开发者预览 - 与 Chrome 合作,Angular DevTools 支持直接在 Chrome DevTools 中自定义 Angular 报告,调试体验提升
- 开发体验优化:风格指南更新、宿主绑定类型检查和语言服务支持、模板支持未标记模板字符串表达式、默认开启模板热模块替换等
- GenAI 相关能力提升,llms.txt 及 angular.dev 提供生成式 AI 应用开发指南和视频
- 发起官方 Angular 吉祥物的意见征集
响应式特性正式稳定
过去三年我们重新思考了 Angular 的响应式模型,使其更健壮、更具前瞻性。在 v16 中我们发布了 Angular Signals 的开发者预览版,随后在 Google 内外都得到了广泛采用。
YouTube 在大会上分享了他们如何通过 Angular Signals 与 Wiz 提升输入延迟,在 Living Room 设备上提升了 35%。同时,TC39 也启动了将 Signals 引入 JavaScript 语言的调研,参考实现 基于 Angular Signals。
我们收集了 RFC 反馈并不断迭代,实现了 signal
、computed
、input
和视图查询 API 的稳定。今天,我们宣布 effect
、linkedSignal
和 toSignal
也已稳定。
全新实验性 API
为了解决 Angular 异步状态管理问题,我们在 v19 推出了 resource API。此后又引入了资源流和 httpResource
,让你可以用基于 Signal 的响应式 API 发起 HTTP 请求。这两个 API 在 v20 中作为实验性功能提供。
resource
API 允许你在 signal 变化时发起异步操作,并将结果暴露为 signal:
const userId: Signal<string> = getUserId(); const userResource = resource({ params: () => ({id: userId()}), loader: ({request, abortSignal}): Promise<User> => { // fetch 会在 AbortSignal 触发时取消未完成的 HTTP 请求 return fetch(`users/${request.id}`, {signal: abortSignal}); }, });
上述代码会在 userId
signal 变化时获取对应用户。
假设我们要通过 WebSocket 获取数据,可以使用流式资源:
@Component({ template: `{{ dataStream.value() }}` }) export class App { // WebSocket 初始化逻辑... // ... // 初始化流式资源 dataStream = resource({ stream: () => { return new Promise<Signal<ResourceStreamItem<string[]>>>((resolve) => { const resourceResult = signal<{ value: string[] }>({ value: [], }); this.socket.onmessage = event => { resourceResult.update(current => ({ value: [...current.value, event.data] })); }; resolve(resourceResult); }); }, }); }
这个最小示例声明了一个流式资源,返回一个 signal 的 Promise。signal 的值类型为 ResourceStreamItem<string[]>
,即 signal 可以持有 { value: string[] }
或 {error: … }
(用于错误)。
我们通过 resourceResult
signal 推送 WebSocket 接收到的数据。
基于此模式,我们还发布了实验性的 httpResource
:
@Component({ template: `{{ userResource.value() | json }}` }) class UserProfile { userId = signal(1); userResource = httpResource<User>(() => `https://example.com/v1/users/${this.userId()}` ); }
上述代码会在 userId
变化时向指定 URL 发起 HTTP GET 请求。httpResource
返回 HttpResourceRef
,其 value
属性为 signal,可直接在模板中访问。userResource
还包含 isLoading
、headers
等属性。
底层实现上,httpResource
使用 HttpClient
,你可以在 HttpClient
provider 中指定拦截器:
bootstrapApplication(AppComponent, {providers: [ provideHttpClient( withInterceptors([loggingInterceptor, cachingInterceptor]), ) ]});
Zoneless 进入开发者预览
过去半年我们在 zoneless 方面取得了很多进展,尤其是在服务端渲染和错误处理方面。
许多开发者即使没有意识到也在用 Zone.js 捕获应用错误。Zone.js 还让框架知道何时可以将服务端渲染应用刷新到客户端。在 zoneless 世界中,我们需要为这些问题找到更健壮的解决方案。
v20 现在 内置 了 Node.js SSR 下 unhandledRejection
和 uncaughtException
的默认处理器,防止因错误导致 node 服务崩溃。
在客户端,你可以在 providers 中引入 provideBrowserGlobalErrorListeners
。现在就可以通过如下方式体验 zoneless:
bootstrapApplication(AppComponent, {providers: [ provideZonelessChangeDetection(), provideBrowserGlobalErrorListeners() ]});
此外,请确保从 angular.json
移除 zone.js polyfill。更多 zoneless 迁移和优势请见 官方文档。
如果你新建 Angular 项目,可以在 CLI 中直接选择 zoneless:
服务端能力进一步夯实
v20 也重点打磨了旗舰级服务端渲染特性——增量水合和路由级渲染模式配置。现在,这两项都已稳定!
增量水合让你的应用更快:只在特定触发时下载并水合页面的一部分,用户无需一次性下载全部 JS,只需按需加载。
现在可通过如下方式启用增量水合:
import { provideClientHydration, withIncrementalHydration } from '@angular/platform-browser'; // ... provideClientHydration(withIncrementalHydration());
组件模板中可使用可延迟视图:
@defer (hydrate on viewport) { <shopping-cart/> }
这样,Angular 会在购物车组件进入视口时下载并水合该部分 UI 及其依赖。
此外,路由级渲染模式配置也已成为稳定 API!如果不同路由有不同渲染需求,可在服务端路由配置中指定:
export const routeConfig: ServerRoute = [ { path: '/login', mode: RenderMode.Server }, { path: '/dashboard', mode: RenderMode.Client }, { path: '/product/:id', mode: RenderMode.Prerender, async getPrerenderParams() { const dataService = inject(ProductService); const ids = await dataService.getIds(); // ["1", "2", "3"] // `id` 用于替换路由路径中的 `:id` return ids.map(id => ({ id })); } } ];
如上配置,登录页服务端渲染,仪表盘客户端渲染,商品页预渲染。
注意商品页需要 id 参数。我们可用异步函数 getPrerenderParams
获取所有 id,返回对象的键映射到路由参数。
你可以将 SSR 应用部署到大多数云服务。我们与 Firebase App Hosting 深度合作,支持混合渲染(SSR、SSG、CSR),并提供 Google Cloud 的安全与可扩展性。
开发体验持续打磨
v20 开发期间我们投入大量精力提升工程质量——全面打磨现有 API,提升开发体验,涵盖框架、路由、表单、http 等。下面分享部分成果:
Chrome DevTools 性能洞察
为进一步提升开发体验并提供更深入的性能洞察,我们与 Chrome DevTools 团队合作,将 Angular 专属性能数据集成到 Performance 面板。以往开发者需在框架专属分析器和浏览器 DevTools 间切换,难以关联信息、定位瓶颈,尤其在生产代码被压缩后。现在,Angular 运行时数据(如组件渲染、变更检测周期、事件监听执行)可与浏览器性能指标同轨展示。
该集成自 v20 起可用,利用 Performance 面板扩展 API,特别是 console.timeStamp
,开销极低,不影响应用性能。开发者可清晰区分自己编写的 TypeScript 代码和 Angular 编译生成代码。只需在应用或 DevTools 控制台运行 ng.enableProfiling()
即可启用。此功能让性能分析更直观全面,助力打造更高性能的 Angular 应用。
如上图所示,性能时间线底部有专属 Angular 轨道。彩色条可预览组件实例化、变更检测等。Angular DevTools 和 Chrome 性能面板用的是同一套钩子,区别在于 Chrome 能将应用生命周期与框架外的 JS 调用关联。
此外,Chrome 性能面板中的 Angular 轨道还显示一些 Angular DevTools 暂无的数据,如组件和 provider 实例化。
框架功能增强
动态创建 Angular 组件可用 createComponent
。v20 新增功能支持为动态组件应用指令并指定绑定:
import {createComponent, signal, inputBinding, outputBinding} from '@angular/core'; const canClose = signal(false); const title = signal('My dialog title'); // 创建 MyDialog createComponent(MyDialog, { bindings: [ // 将 signal 绑定到 canClose 输入 inputBinding('canClose', canClose), // 监听对话框的 onClose 事件 outputBinding<Result>('onClose', result => console.log(result)), // title 属性与 title signal 双向绑定 twoWayBinding('title', title), ], directives: [ // 应用 FocusTrap 指令,无需绑定 FocusTrap, // 应用 HasColor 指令,并将 color 输入绑定为 'red' // inputBinding 回调在每次变更检测时调用 { type: HasColor, bindings: [inputBinding('color', () => 'red')] } ] });
如上,我们创建了一个对话框组件并指定:
canClose
输入绑定,传入 signalcanClose
- 将
onClose
输出设置为日志回调 title
属性与 signal 双向绑定
此外,还为组件添加了 FocusTrap
和 HasColor
指令。注意我们还可以为 HasColor
指令指定输入绑定。
模板表达式语法扩展
我们一直在缩小 Angular 模板表达式与完整 JavaScript 语法的差距,提升表达力和开发体验。现在支持指数运算符 **
和 in
运算符:
<!-- n 的平方 --> {{ n ** 2 }} <!-- 检查 person 对象是否包含 name 属性 --> {{ name in person }}
v20 还支持在表达式中直接使用未标记的模板字符串:
<div [class]="`layout col-${colWidth}`"></div>
诊断能力增强
为防止常见错误,我们引入了静态检查,检测 无效的空值合并、结构型指令缺失导入,以及 未调用 track
函数 的警告:
@Component({ template: ` @for (user of users; track trackFn) { <!-- ... -> } ` }) class UserList { users = getUsers(); trackFn() { // ... 逻辑 } }
Angular 模板中的 @for
循环支持 track 表达式。实际上,trackFn
本身是返回函数的表达式,但大多数情况下我们希望调用 trackFn
,新诊断能更容易发现此类错误。
风格指南更新
我们参考了过去十年数千个 Angular 应用的实践,决定更新风格指南,目标是现代化并去除不必要的复杂性。
收集 RFC 反馈后,我们简化了风格指南——移除非 Angular 专属的代码健康建议,将与编码风格无关的最佳实践迁移到文档。文件名和类名后缀也变为可选,鼓励更有意图的命名,减少样板代码。
从 v20 起,Angular CLI 默认不再为组件、指令、服务和管道生成后缀。对于已有项目,ng update
会通过更新 angular.json
启用后缀生成。新项目如需启用后缀,可用如下 schematic 配置:
{ "projects": { "app": { ... "schematics": { "@schematics/angular:component": { "type": "component" }, "@schematics/angular:directive": { "type": "directive" }, "@schematics/angular:service": { "type": "service" }, "@schematics/angular:guard": { "typeSeparator": "." }, "@schematics/angular:interceptor": { "typeSeparator": "." }, "@schematics/angular:module": { "typeSeparator": "." }, "@schematics/angular:pipe": { "typeSeparator": "." }, "@schematics/angular:resolver": { "typeSeparator": "." } }, } } }
Angular 这些年发展很大,我们也希望风格指南能反映这种变化。因此,大部分与 NgModules 相关的建议被移除,@HostBinding
和 @HostListener
的使用也推荐用指令元数据中的 host
对象替代。为保证新指南下开发体验不倒退,我们还补齐了宿主绑定支持的部分短板。
宿主绑定能力提升
历史上推荐 @HostBinding
和 @HostListener
,是因为它们比 host
对象有更好的编辑器支持。但它们难以发现、需要装饰器,代码也更繁琐。
v20 引入了宿主绑定和监听表达式的类型检查与语言服务支持。
下图演示了该特性。首先因调用了 getAppTile
(应为 getAppTitle
)报错,修正后又因传参类型不符报错。
启用方法:在 tsconfig.json
的 angularCompilerOptions
下设置 typeCheckHostBindings
为 true
。v21 起将默认开启。
Angular DevTools 支持增量水合调试
为简化增量水合和可延迟视图的调试,现在可直接在 Angular DevTools 预览它们!
下图展示了如何检查 defer 块及其后续加载内容。
使用 defer 块配合增量水合时,还会有图标指示当前组件是否已水合。
Vitest 实验性支持
Karma 废弃后,我们与测试框架作者合作,寻找支持浏览器测试的替代品。我们合并了一个 PR,创建了实验 playground 以尝试不同测试运行器。
v20 起,Angular CLI 实验性支持 vitest,具备 watch 模式和浏览器测试!
在 node 环境下试用 vitest:
npm i vitest jsdom --save-dev
然后在 angular.json 中更新测试配置:
"test": { "builder": "@angular/build:unit-test", "options": { "tsConfig": "tsconfig.spec.json", "buildTarget": "::development", "runner": "vitest" } }
接着需在单元测试文件中引入正确的 import:
... import { describe, beforeEach, it, expect } from 'vitest'; ...
最后,运行 ng test 即可用 vitest 执行单元测试。
Angular Material 体验提升
本次发布进一步打磨了按钮组件,更好地对齐 M3 规范。
Tonal 按钮
部分更新:
- 实现了 tonal 按钮
- 术语与 M3 规范对齐
- 支持设置按钮默认外观
- 为图标按钮新增
matIconButton
选择器,保持一致性
其它体验提升:
- 对话框新增
closePredicate
,社区呼声极高 - Overlay API 新增 更好 tree-shaking
- 自动处理 `prefers-reduced-motion` 动画
- 新增 DI token 可 禁用动画
MatButton
和MatAnchor
合并,无需分别导入
支持 GenAI 开发者
为让大模型生成现代 Angular 代码、助力 GenAI 应用开发,我们启动了两项工作:
- 维护
llms.txt
文件(见 GitHub PR),帮助大模型发现最新文档和代码示例 - 为构建 GenAI 应用的开发者提供起步指南
部分大模型仍生成旧语法(如结构型指令、NgModules),我们通过 llms.txt
逐步解决。未来还会持续提供最新语法示例,并探索系统提示以引导 LLM 使用正确 API。
第二项工作是为构建 AI 功能 API 的开发者提供最佳实践。我们举办了多场 直播,演示如何在 Angular 应用中集成 Genkit 和 Vertex AI。相关示例已 开源,最佳实践见 angular.dev/ai。
这只是让 Angular 成为智能 AI 应用首选的起点。
NgIf、NgFor、NgSwitch 弃用
我们在 v17 推出了内置控制流,带来如下提升:
- 更贴近 JavaScript 的直观语法
- 无需再导入模块或指令,使用更简单
- 通过优化 diff 算法提升性能
- 类型收窄带来更好的类型检查
我们还提供了 schematic,一行命令即可将项目从结构型指令迁移到内置控制流:
ng generate @angular/core:control-flow
目前,HTTP Archive 公共数据集中一半以上的 v17+ 应用已采用新语法!
基于社区反馈和采用率,未来我们将弃用 *ngIf
、*ngFor
和 *ngSwitch
,鼓励大家使用最新内置控制流。根据弃用政策,这些结构型指令将在 v22(约一年后)移除。
官方 Angular 吉祥物
随着 Angular 不断发展壮大,我们很高兴宣布一项新举措:打造官方吉祥物!虽然 Angular 已是知名框架,但一直缺少像其他开源项目那样有趣的视觉形象。我们听到了大家想要毛绒玩具、钥匙扣等实体的呼声,因此开启了这次创意之旅。我们与 Dart 和 Firebase 吉祥物设计师合作,结合 Angular 的核心理念和社区精神,提出了三套初步方案。
现在轮到你们——Angular 社区——参与进来!秉承包容和社区驱动的价值观,我们向所有人开放吉祥物征集。官方 RFC 见 goo.gle/angular-mascot-rfc。
以下是初步概念图:
“Angular 形状角色”,灵感来自官方 Logo
睿智坚韧的“琵琶鱼”(比现实中的 琵琶鱼可爱多了!)
琵琶鱼的另一种变体。
欢迎大家查看完整 RFC,为方案投票、提出建议,甚至命名。你们的反馈一直是 Angular 进步的动力,也希望同样的社区精神能决定我们的吉祥物。让我们一起创造属于 Angular 的独特形象!
感谢所有贡献者!
全球有成千上万的库作者、会议和社区组织者、教育者等,正通过 Angular 推动 Web 进步!没有你们就没有今天的 Angular。
自 v19 发布以来,框架、组件和 CLI 共收到并合并了 225+ 人的贡献!每一次改动都让 Angular 更好。以下是部分社区成员贡献的特性:
- Domenico Gemoli 为
AbstractControl
新增markAllAsDirty
,可将组件及其所有子组件标记为 dirty - Enea Jahollari 实现 了未调用 track 函数的诊断
- Enea Jahollari 还 新增 了模板自闭合标签迁移
- Jeri Peier 实现 了表单校验器支持 Set
- Kevin Brey 增强 了结构型指令缺失导入的诊断
- Lukas Spirig 引入 了 RouterLink 的自定义元素支持
- Meddah Abdallah 实现 了路由异步重定向
- Younes Jaaidi 让测试支持 AOT 编译,见 Jest 和 Karma
感谢所有人 🙏
展望未来!
v20 我们为过去几年启动的重大工程(响应式、zoneless、增量水合、框架和表单 API)做了大量打磨,也初步勾勒了 selectorless、signal-forms、单元测试、官方吉祥物等未来方向。
Angular 是为你而生,你的反馈决定我们前进的方向。随着这些重大项目的高层规划逐步成型,我们会持续发布更新和征求意见。现在,请务必为官方吉祥物的未来发表你的看法!我们非常期待找到能象征 Angular 产品和价值观的形象。欢迎在 RFC 留言!
下次再见,感谢你成为 Angular 旅程的一部分 🙏
最新评论