Angular v16 正式发布!_angular官网

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

前言

六个月前,独立 APIs 从开发者预览版到 v15 中的正式稳定,这是 Angular 易用性和开发体验方面上升到的一个重要的里程碑。今天,我们很高兴地与大家分享,我们正在继续推进 Angular ,这是自 Angular 首次推出以来最大的一次发布;在响应式、服务器端渲染和工具方面取得了巨大的飞跃。所有这些都伴随着几十项功能请求的质量 改进 ,在 GitHub 上总共有 2500 多个赞!

这篇文章包含了很多内容,记录了我们在过去六个月里所做的大部分改进。如果您想快速了解情况,请观看以下视频:
https://youtu.be/bkOEMw0oTkY

重新思考响应式(Rethinking Reactivity)

作为 v16 版本的一部分,我们很高兴能分享 Angular 全新 Reactivity 模型的开发者预览版,该模型为性能和开发体验带来了重大改进。

它与当前系统完全向后兼容(backward compatible)并与 RxJS 可互操作(interoperable),实现了:

  • 通过减少变更检测过程中的计算次数,提高了运行时性能。一旦 Angular Signals 完全推出,我们预计使用 signals 构建的应用程序的 INP Core Web Vital 指标将有显著改进
  • 为 Reactivity 提供了一个更简单的心智模型,使视图的依赖关系以及应用程序中的数据流更加清晰
  • 启用了细粒度响应式 (fine-grained reactivity) , 使我们能够在未来的版本中仅检查受影响组件的变化
  • 使 Zone.js 在未来变成可选项,通过 signals 通知框架哪些 Model 已经变化
  • 计算属性 (computed properties) 使其在变更检测周期中不需要重新计算
  • 通过引入 plan to introduce reactive inputs 实现了更好 RxJS 互操作性 interoperability with RxJS

最初的 GitHub discussion 获得了 682 条评论,之后我们分享了 一系列 RFCs 收到了 1,000 多条评论!

在 v16 版本中,你可以从 @angular/core 中找到响应式类库部分特性以及与 RxJS 的互操作包 — @angular/core/rxjs-interop ,框架中的完整信号集成将于今年晚些时候实现。

Angular Signals

Angular Signals 库允许你定义 Reactive 值并表达它们之间的依赖关系。你可以在 相应的 RFC 中了解更多关于库的特性。下面是一个如何将其与 Angular 一起使用的简单示例:

@Component({
  selector: 'my-app',
  standalone: true,
  template: `
    {{ fullName() }} 
  `,
})
export class App {
  firstName = signal('Jane');
  lastName = signal('Doe');
  fullName = computed(() => `${this.firstName()} ${this.lastName()}`);

  constructor() {
    effect(() => console.log('Name changed:', this.firstName()));
  }

  setName(newName: string) {
    this.firstName.set(newName);
  }
}

如上代码段创建了一个计算属性值 fullName , 它依赖firstNamelastName signals,我们也声明了一个 effect, 它的回调函数将会在其读取的信号值每次更新时执行,也就是 firstName 更改时重新执行,以上的 fullName 计算属性意味着它会依赖firstNamelastName信号值的变化。

当我们设置firstName为 "John" 时,浏览器会打印如下的日志:

"Name changed: John Doe"

RxJS 互操作性 (interoperability)

你将能够通过 @angular/core/rxjs-interop 中的函数轻松地将 signals 转换为 observables,该函数作为 v16 开发预览版中的一部分!

这是如何转换 signal 为 observable 的示例:

import { toObservable, signal } from '@angular/core/rxjs-interop';

@Component({...})
export class App {
  count = signal(0);
  count$ = toObservable(this.count);

  ngOnInit() {
    this.count$.subscribe(() => ...);
  }
}

下面是一个如何将 observable 的转换为 signal 以避免使用async管道的示例:

import {toSignal} from '@angular/core/rxjs-interop';

@Component({
  template: `
    
  • {{ row }}
  • ` }) export class App { dataService = inject(DataService); data = toSignal(this.dataService.data$, []); }

    Angular 用户通常希望在相关 Subject 完成时完成一个流。以下模式非常常见:

    destroyed$ = new ReplaySubject(1);
    
    data$ = http.get('...').pipe(takeUntil(this.destroyed$));
    
    ngOnDestroy() {
      this.destroyed$.next();
    }

    我们介绍一种新的 RxJS 操作符 takeUntilDestroyed ,简单使用示例如下:

    data$ = http.get('…').pipe(takeUntilDestroyed());

    默认情况下,此操作符将注入当前的清理上下文。假如在组件中使用,它将使用组件的生命周期。

    当你想要将 Observable 的生命周期与特定组件的生命周期联系起来时, takeUntilDestroy 特别有用。

    原理是基于下文的 DestroyRef 实现。

    signals 下一阶段 (Next steps for signals)

    接下来我们将研究 基于信号组件 ,信号组件将会简化生命周期钩子函数以及一种简单的声明式输入(inputs)和输出(outputs),我们还将编写一套更完整的示例和文档。

    Angular 仓储中最受欢迎的 Issue 是 “ Proposal: Input as Observable ”。几个月前我们回应说要支持这个特性为框架中努力的一部分,我们很高兴与大家分享,今年晚些时候,我们将推出一项功能,该功能将启用基于信号的输入—— 你将能够通过 interop 包将输入转换为可观测值。

    服务器端渲染和 Hydration 增强

    根据我们的年度开发者调查,服务器端渲染是 Angular 改进的重要机会。在过去的几个月里,我们与 Chrome Aurora 团队合作,提高了 Hydration 和服务器端渲染的性能和 DX。今天我们很高兴与大家分享全应用无损 Hydration 的开发者预览版!

    在新的全应用程序无损 Hydration 中,Angular 不再从头开始重新渲染应用程序。相反,框架在构建内部数据结构时查找现有的 DOM 节点,并将事件侦听器附加到这些节点。

    好处是:

    • 用户的页面上没有闪烁的内容
    • 在某些场景下提供更好的 Web Core Vitals
    • 我们将在今年晚些时候推出经得起未来考验的体系结构,该体系结构能够使用原语加载细粒度代码。目前,处于渐进的惰性路由 hydration 中
    • 只需几行代码即可轻松与现有应用程序集成(请参阅下面的代码片段)
    • 在执行手动 DOM 操作的组件的模板中,使用 ngSkipHydration 属性逐步采用 Hydration

    在早期测试中,我们看到 Largest Contentful Paint 通过全应用程序 Hydration 作用提高了45%!

    一些应用已经在生产中实现了 Hydration,并报告了 CWV 的改进:

    开始体验只需要在main.ts中添加如下几行代码即可:

    import {
      bootstrapApplication,
      provideClientHydration,
    } from '@angular/platform-browser';
    
    ...
    
    bootstrapApplication(RootCmp, {
      providers: [provideClientHydration()]
    });
    

    你可以在 documentation 中查找更多详细信息。

    新的服务器端渲染特性

    作为 v16 版本的一部分,我们还更新了 Angular Universal 的 ng-add 原理图,使你能够使用独立 API 将服务器端渲染添加到项目中。我们还为内联样式引入了对更严格的 内容安全策略的支持 。

    Hydration 和服务端渲染的下一步

    v16 中的工作只是一块垫脚石,我们计划在这里做更多的工作。在某些情况下,有机会延迟加载对页面不重要的 JavaScript,并在以后对相关组件进行 Hydrate。这种技术被称为部分 Hydrate,我们将在下一步对其进行探索。

    自从 Qwik 从谷歌的封闭源代码框架 Wiz 中推广了可恢复性的想法以来,我们在 Angular 中收到了许多关于这一功能的请求。可恢复性肯定在我们的路线图上,我们正在与 Wiz 团队密切合作,探索更多的空间。我们对它所带来的开发人员体验的限制持谨慎态度,评估不同的权衡,并将在我们取得进展时随时向你通报。

    你可以通过阅读 “What’s next for server-side rendering in Angular” 查看更多未来的计划。

    改进对独立组件/指令/管道的工具

    Angular 是一个被数百万开发人员用于许多关键使命的应用程序框架,我们认真对待重大变更,我们 几年前 就开始探索独立的 APIs,2022 年我们在开发者预览下发布了它们,现在,经过一年多的收集反馈和对 APIs 的迭代,我们希望被更广泛的采用!

    为了支持开发人员将其应用程序转换为独立 APIs,我们开发了迁移原理图和 独立组件迁移指南 ,你进入项目执行:

    ng generate @angular/core:standalone

    原理图将转换你的代码,删除不必要的 NgModules类,最后将项目的引导程序更改为使用独立的 APIs。

    独立 ng new 集

    作为 Angular v16 的一部分,你可以一开始就创建一个新的独立项目,要尝试独立 APIs 原理图的开发预览版,请确保你在 Angular CLI v16 上并运行:

    ng new --standalone

    你将在没有任何NgModules的情况下获得更简单的项目目录,此外,项目中的所有生成器都将生成独立的指令、组件和管道!

    配置 Zone.js

    在独立 APIs 首次发布后,我们从开发人员那里听说,你希望能够使用新的 bootstrapApplication API 配置 Zone.js。

    我们通过
    provideZoneChangeDetection
    添加了一些选项:

    bootstrapApplication(App, {
      providers: [provideZoneChangeDetection({ eventCoalescing: true })]
    });
    

    推进开发者工具

    现在,让我们分享 Angular CLI 和语言服务的一些功能亮点。

    基于 esbuild 的构建系统的开发预览版

    一年多前,我们宣布正在 Angular CLI 中对 esbuild 进行实验性支持,以加快构建速度。今天,我们很高兴与大家分享,在 v16 中,我们基于 esbuild 的构建系统进入了开发预览版! 早期测试显示,冷生产环境构建改善了 72% 以上。

    ng serve 中,我们现在使用 Vite 作为开发服务器,esbuild 提供在开发和生产环境的构建。

    我们想强调的是,Angular CLI 完全依赖 Vite 作为开发服务器。为了支持选择器匹配,Angular 编译器需要维护组件之间的依赖关系图,这需要与 Vite 不同的编译模型。

    你可以通过更新 angular.json 来尝试 Vite + esbuild :

    ...
    "architect": {
      "build": {                     /* Add the esbuild suffix  */
        "builder": "@angular-devkit/build-angular:browser-esbuild",
    ...

    接下来,在我们将这一特性从开发者预览提升到正式版之前,我们将解决对 i18n 的支持问题。

    使用 Jest 和 Web Test Runner 进行更好的单元测试

    根据 Angular 和更广泛的 JavaScript 社区的开发人员调查, Jest 是最受欢迎的测试框架和测试运行环境之一,我们收到了许多支持 Jest 的请求,由于不需要真正的浏览器,Jest 的复杂性降低了。

    今天,我们很高兴地宣布,我们将引入实验性的 Jest 支持。在未来的版本中,我们还将把现有的 Karma 项目转移到 Web Test Runner ,以继续支持基于浏览器的单元测试。对于大多数开发人员来说,无需任何操作。

    通过 npm install jest --save-dev 安装 Jest 并且更改 angular.json 你就能在新项目中体验 Jest:

    {
      "projects": {
        "my-app": {
          "architect": {
            "test": {
              "builder": "@angular-devkit/build-angular:jest",
              "options": {
                "tsConfig": "tsconfig.spec.json",
                "polyfills": ["zone.js", "zone.js/testing"]
              }
            }
          }
        }
      }
    }

    你可以在我们 最近的博客文章 中了解更多关于我们未来单元测试策略的信息。

    自动完成模板中的导入

    你使用模板中的组件或管道从 CLI 或语言服务中获得错误的次数是多少次,而实际上没有导入相应的实现?我猜应该是很多次!

    语言服务现在允许自动导入组件和管道。

    如上动图显示了 VSCode 中 Angular 语言服务的自动导入功能。

    还有更多

    在 v16 我们启用了 TypeScript 5.0 的支持, 同时支持 了 ECMAScript decorators , 移除了 ngcc 的开销,添加了 service workers 和 app shell 在独立应用的支持, 扩展了 CSP support in the CLI , 以及更多 !

    改善开发者体验

    除了我们重点关注的大型计划外,我们还致力于引入备受要求的功能。

    输入必填(Required inputs)

    自从我们在 2016 年引入 Angular 以来,如果不为特定输入指定值,就不可能出现编译时错误。由于 Angular 编译器在构建时执行检查,因此此更改在运行时增加了零开销,多年来,开发人员一直在要求这个功能,我们得到了一个强有力的指示,这将非常方便!

    在 v16 中,可以根据需要标记输入为 required :

    @Component(...)
    export class App {
      @Input({ required: true }) title: string = '';
    }
    

    将路由器数据作为组件输入进行传递

    路由的开发经验一直在快速发展,GitHub 上一个 流行的功能请求 是要求能够将路由参数绑定到相应组件的输入。我们很高兴与大家分享这一功能现已作为 v16 版本的一部分提供!

    现在,可以将以下数据传递给路由组件的输入:

    • 路由 data — resolvers 和 data 属性
    • Path 参数
    • Query 参数

    以下是如何访问路由 resolver 数据的示例:

    const routes = [
      {
        path: 'about',
        loadComponent: import('./about'),
        resolve: { contact: () => getContact() }
      }
    ];
    
    @Component(...)
    export class About {
      // The value of "contact" is passed to the contact input
      @Input() contact?: string;
    }
    

    CSP 对内联样式的支持

    Angular 在组件样式的 DOM 中包含的内联样式元素违反了默认 style-src 内容安全策略(CSP) 。要解决此问题,它们应该包含一个 nonce 属性,或者服务器应该在 CSP 头中包含样式内容的哈希。尽管在谷歌,我们没有发现针对该漏洞的有意义的攻击向量,但许多公司实施了严格的 CSP,导致 Angular 仓储上的 功能请求 广受欢迎。

    在 Angular v16 中,我们实现了一个跨越框架、Universal、CDK、Material 和 CLI 的新功能,该功能允许你为 Angular 内联的组件的样式指定 nonce 属性。有两种方法可以指定 nonce:使用 ngCspNonce 属性或通过 CSP_NONCE 注入令牌。

    如果您有权访问服务器端模板,则 ngCspNonce 属性非常有用,该模板可以在构造响应时将 nonce 添加到标头和 index.html 中。

    
    
        
    
    

    另一种方法是通过CSP_NONCE注入令牌指定 nonce。如果你在运行时可以访问 nonce,并且希望能够缓存 index.html,请使用此方法:

    import {bootstrapApplication, CSP_NONCE} from '@angular/core';
    import {AppComponent} from './app/app.component';
    
    bootstrapApplication(AppComponent, {
      providers: [{
        provide: CSP_NONCE,
        useValue: globalThis.myRandomNonceValue
      }]
    });
    

    灵活的 ngOnDestroy

    Angular 的 Lifecycle Hooks 提供了大量的功能,可以插入应用程序执行的不同时刻,如何实现更高的灵活性是一种机会和选择,例如,提供对 OnDestroy as an observable 访问作为一种可观察的方式。

    在 v16 中,我们使 OnDestroy 可以被注入,此功能实现了开发人员一直要求的灵活性。新功能允许你注入与组件、指令、服务或管道相对应的DestroyRef ,并注册onDestroy 生命周期钩子函数。DestroyRef 可以被注入到注入上下文中的任何位置,包括组件之外 —— 在这种情况下,当相应的注入器被销毁时,ngDestroy 钩子就会被执行:

    import { Injectable, DestroyRef } from '@angular/core';
    
    @Injectable(...)
    export class AppService {
      destroyRef = inject(DestroyRef);
    
      destroy() {
        this.destroyRef.onDestroy(() => /* cleanup */ );
      }
    }
    

    自动关闭标签 (Self-closing tags)

    我们最近实现的一个 备受要求的功能 ,允许你对 Angular 模板中的组件使用自闭标签,这是一个小的开发体验改进,可以为你节省一些打字时间!

    现在您可以替换:

    
    

    为:

    更好、更灵活的组件

    在过去的几个季度里,我们与谷歌的 Material Design 团队密切合作,为 Angular Material 的 Web 提供了 Material 3 实现。我们在 2022 年交付的基于 MDC Web 的组件为这项工作奠定了基础。

    作为下一步,我们正在努力在今年晚些时候推出一个基于 expressive token-based 的主题化 API,该 API 支持 Angular Material 组件的更高定制。

    提醒一下,我们将在 v17 中删除遗留的、非基于 MDC 的组件,请确保你按照我们的 迁移指南 进行迁移,以获得最新版本。

    继续我们的无障碍倡议

    遵循谷歌的使命,Angular 可以让你为每个人构建 Web 应用程序!这就是为什么我们不断投资,为 Angular CDK 和 Material 组件提供 更好的可访问性 。

    社区贡献亮点

    我们想强调的社区引入的两个功能是:

    • 使用ngSkipHydration的 扩展诊断 - 来自 Matthieu Riegler
    • 引入provideServiceWorker 在没有 NgModules 的情况下使用 Angular service worker - 来自 Julien Saguet

    超过 175 人在 GitHub 上为 v16 做出了贡献,数千人通过博客文章、讲座、播客、视频、对响应式 RFC 的评论等做出了贡献。

    我们想对所有帮助我们使本次发布变得特别的人表示衷心的感谢。

    让我们一起保持势头猛进!

    版本 16 是 Angular 在未来一年的响应式和服务器端渲染改进的垫脚石。我们将通过在开发体验和性能方面进行创新,推动 Web 向前发展,同时使你能够为每个人构建!

    你可以成为 Angular Momentum 的一部分,并通过在即将到来的 RFC、调查或社交媒体中分享你的想法来帮助我们塑造框架的未来。

    感谢您成为 Angular 社区的一员,我们迫不及待地想让你尝试这些功能!??

    参考资料

    原文链接:
    https://blog.angular.io/angular-v16-is-here-4d7a28ec680d

    原文作者:Minko Gechev

    参考翻译:
    https://zhuanlan.zhihu.com/p/626686500