[译]认识 Angular v19

504 阅读22分钟

在过去的两年里,我们加倍加大了对开发人员体验和性能的投资 —— 在每一个版本中,我们都在不断地进行改进,这些改进结合在一起会产生更大的影响。

看到社区的积极响应和开发者活动参与度的提高,证明我们一直在朝着正确的方向前进。

今天的版本带来了一系列改进,让您能够更加轻松、自信地交付快速的 Web 应用程序。

几个亮点:

  • 增量水合的开发者预览版支持对性能要求最高的用例
  • 控制在客户端、服务器上或构建期间渲染哪些路由,并在预渲染期间解析路由参数
  • 原理图让您了解最新的最佳实践—— input()output()view queries 、基于 inject() 的依赖注入和新的构建系统
  • 核心反应原语的稳定并引入新的原语:linkedSignalresource
  • 一系列生活质量改进解决了 GitHub 上超过 2,700 个 👍 的功能请求!这包括时间选择器组件、未使用的导入删除、通过语言服务运行原理图、样式的 HMR 等等!

您可以从我们的特别活动视频中观看此次发布的快速概述。有关 v19 的功能和改进的更全面列表,请继续阅读下文。

YouTube: Angular v19 Developer Event

为速度而打造

在不断发展的 Angular 中,我们看到了实现开箱即用的最佳性能实践的机会,因此我们可以支持您对性能敏感的用例。

在过去的两年里,我们启动了一个支持 zoneless Angular 的项目,使服务器端渲染成为 Angular CLI 的一个组成部分,并与 Chrome Aurora 在水合作用和图像指令方面密切合作。

在版本 19 中,我们通过增量水合、服务器路由配置、默认启用的事件重播等将 Angular 服务器端渲染提升到另一个水平。

构建大型 Web 应用程序会增加我们发送给用户的 JavaScript 数量,从而对用户体验产生负面影响。 在第 17 版中,我们为客户端渲染的应用程序提供了可延迟视图,使其可以轻松加载代码。 对于服务器端呈现的应用程序,我们引入了全应用程序水合,这需要与给定页面相关的所有 JavaScript,以使其具有交互性。 今天,我们将从可延迟视图中汲取灵感,为服务器提供一种解决方案!

欢迎在开发者预览版中增加水合作用

增量水合允许您使用已经熟悉的 @defer 语法来注释模板的某些部分,指示 Angular 在特定触发器上延迟加载和水合它们。

增量水合演示

上面的演示显示了服务器端渲染页面的增量水合作用。我们在演示应用程序代码中添加了三种视觉效果,以更好地说明正在发生的情况:

  • 灰度滤镜中的一个组件显示 Angular 尚未加载并水合它
  • 当组件开始发出脉冲特效时,Angular 会从网络下载该组件
  • 当组件周围有紫色边框并且组件不再具有灰度滤镜时,Angular 已下载并水合组件

此外,演示应用程序为每个加载操作人为延迟了 500 毫秒,以便我们可以轻松探索不同的状态。

请注意,一开始除了顶部栏之外的所有内容都是灰色的。这意味着此时我们尚未下载与该页面关联的任何 JavaScript。

当用户使用左上角的过滤器组件时,Angular 会下载它(通过脉冲直观地指示),然后将其水合(通过组件周围的紫色光芒来证明)。

随后,我们继续与页面交互并逐步水合其余组件。

即使没有人为延迟,Angular 也会异步下载和水合组件,这意味着我们必须重播用户事件。对于此功能,我们使用我们在 Angular 版本 18 中引入的事件重播功能,该功能为 Google Search 提供支持。

当您更新到 Angular v19 时,您可以在任何已使用 SSR 和完整应用程序水合的应用程序中尝试新的增量水合。在您的客户端引导程序中,指定:

import { provideClientHydration, withIncrementalHydration } from '@angular/platform-browser';
// ...
provideClientHydration(withIncrementalHydration());

要将增量水合应用到模板,请使用:

@defer (hydrate on viewport) {
  <shopping-cart/>
}

当您的应用程序加载时,Angular 不会下载并填充购物车组件,直到它进入视口。您可以在文档中阅读有关增量水合的更多信息。

我们感谢所有在增量水合 RFC 中分享想法的人以及我们的 Beta 测试人员。感谢您帮助我们改进 Angular!

默认情况下启用事件重播

任何框架中服务器端渲染应用程序的一个常见问题是用户事件与浏览器下载和执行负责处理该事件的代码之间出现的间隙。我们已经在增量水合部分谈到了这一点。

去年五月,我们分享了事件调度库,它解决了这个用例。事件分派在初始页面加载期间捕获事件,并在负责处理事件的代码可用时重放这些事件。事件调度与 Wiz 团队为 Google Search 开发的相同,在过去十年中已经过数十亿用户的实际测试。

您可以通过配置您的水合提供程序来启用 Angular 中的事件重播功能:

// For new projects the Angular CLI will generate this code by default
bootstrapApplication(App, {
  providers: [
    provideClientHydration(withEventReplay())
  ]
});

结果将类似于下面 gif 中的可视化效果。当浏览器第一次渲染应用程序时,它还没有下载任何 JavaScript,我们通过使用 UI 的灰色来可视化这一点。

您可以看到,与此同时,用户多次单击“添加到购物车”按钮。在后台,事件调度记录所有这些事件。

当负责处理点击事件的 JavaScript 加载时,事件分派器会重播反映在购物车中的商品数量的事件:

事件重播演示

在过去的六个月里,我们验证了这种方法对于 Angular 来说确实非常有效。今天,我们将事件重播升级到稳定状态,并默认为所有使用服务器端渲染的新应用程序启用它!

路由级渲染模式

当您在应用程序中启用服务器端渲染时,默认情况下,Angular 将在服务器端渲染应用程序中的所有参数化路由,并预渲染所有不带参数的路由。

在 v19 中,我们提供了一个名为 ServerRoute 的新接口,它允许您配置各个路由是否应在服务器端渲染、预渲染或在客户端渲染:

export const serverRouteConfig: ServerRoute[] = [
  { path: '/login', mode: RenderMode.Server },
  { path: '/dashboard', mode: RenderMode.Client },
  { path: '/**', mode: RenderMode.Prerender },
];

在上面的示例中,我们指定希望 Angular 在服务器上渲染 login 路由,在客户端上渲染 dashboard 路由,并预渲染所有其他路由。服务器路由配置是一个新的配置文件,但它用 glob 组成了现有的路由声明,因此您不必复制任何路由。

过去,没有符合人体工程学的方法来在预渲染时解析路线参数。通过服务器路由配置,现在这是无缝的:

export const routeConfig: ServerRoute = [{
 path: '/product/:id',
 mode: 'prerender',
 async getPrerenderPaths() {
   const dataService = inject(ProductService);
   const ids = await dataService.getIds(); // ["1", "2", "3"]
   return ids.map(id => ({ id })); // `id` is used in place of `:id` in the route path.
  },
}];

由于 Angular 在注入上下文中执行 getPrerenderPaths ,因此您可以使用 inject() 在参数解析中重用业务逻辑。

此功能现已提供开发者预览版!您可以在我们的文档中阅读有关路由级渲染模式的更多信息。

使用 Zoneless Angular 进行服务器端渲染

在 v18 中,我们引入了对 zoneless 的实验性支持,它允许 Angular 在不依赖 zone.js 的情况下运行。从历史上看,zone.js 一直是 Angular 服务器端渲染过程中的关键组件,当框架完成渲染并且页面标记准备就绪时,它会通知服务堆栈。

我们发现等待应用程序的主要原因是待处理的请求和导航。我们引入了一个在 Angular HttpClientRouter 中使用的原语,用于延迟向用户发送页面,直到应用程序准备就绪。今天您就可以在 v19 中尝试这两个软件包和 zoneless!

除此之外,我们还提供了一个 RxJS 运算符,它使您能够通知服务堆栈 Angular 尚未完成渲染:

subscription
  .asObservable()
  .pipe(
    pendingUntilEvent(injector),
    catchError(() => EMPTY),
  )
  .subscribe();

subscription 发出新值时,我们将使应用程序稳定,并且服务堆栈会将渲染的标记传递给客户端。

开发者体验

我们一直致力于让您从一开始就能够构建快速的应用程序。我们认为确保您有效地开发这些应用程序同样重要。今天,我们迫不及待地想与您分享一些令人兴奋的改进!

通过热模块更换进行即时编辑/刷新

Angular v19 支持开箱即用的样式的热模块替换 (HMR),并为标志后面的模板 HMR 提供实验性支持!

在此改进之前,每次您更改组件的样式或模板并保存文件时,Angular CLI 都会重建您的应用程序并向浏览器发送刷新通知。

我们的新 HMR 将编译您修改的样式或模板,将结果发送到浏览器,并修补您的应用程序,而无需刷新页面和任何状态丢失。这样您将拥有更快的周转周期和不间断的流程状态。

Angular CLI 中的热模块替换样式

v19 中默认启用样式的热模块替换!要尝试模板的 HMR,请使用:

NG_HMR_TEMPLATES=1 ng serve

要禁用此功能,请指定 "hmr": false 作为开发服务器选项,或者使用:

ng serve --no-hmr

Standalone 默认为 true

两年多前,我们在 v14 中引入了独立组件。在上次开发者调查中,超过 90% 的开发者表示他们正在使用此功能。作为 v19 的一部分,我们提供了一个原理图,它将作为 ng update 的一部分运行,并将自动删除所有独立指令、组件和管道 standalone 元数据属性,并将所有非独立抽象的 standalone 设置为 false

有关更多信息,请查看 update.angular.dev 上的更新指南。感谢 Matthieu Riegler 的贡献!

严格 Standalone 模式

为了帮助您在项目中实施现代 API,我们开发了一个编译器标志,如果它发现非独立的组件、指令或管道,则会抛出错误。要在项目中启用它,请配置 tsconfig.json

{
  "angularCompilerOptions": {
    "strictStandalone": true
  }
}

测试工具状态

自从我们在 Angular CLI 中引入实验性 JestWeb Test Runner 支持以来,我们继续评估并收集开发人员的反馈。

在单元测试领域,我们相信真正的浏览器测试可以确保我们在测试和生产中拥有相同的环境。为了支持开发人员迁移到新的基于 esbuild 的构建器,在 v19 中,我们引入了 Karma 的开发人员预览支持,以通过设置 builderMode 选项来使用 application 构建器。这缩短了单元测试的构建时间,并允许用户更轻松地利用 application 构建器特定的功能(例如文件加载器),而不会破坏测试。

随着 Karma 被弃用,我们将在 2025 年上半年继续评估现有的测试运行程序,以选择我们将继续执行的默认建议。关注我们的博客X 以获取公告和调查。

从一开始就确保安全

我们与 Google 的安全团队合作开发了开发者预览功能,用于根据 index.html 中的脚本自动生成基于哈希的严格内容安全策略

使用基于哈希的 CSP,浏览器会将每个内联脚本的哈希添加到 CSP。每个脚本都有一个与其关联的唯一哈希值。

这将防止攻击者在您的页面上运行恶意脚本,因为浏览器要执行该脚本,其哈希值需要存在于 CSP 中。

目前,autoCSP 在开发者预览版中作为选项提供。 要在应用程序中使用它,请在 angular.json 项目的 security 下将 autoCSP 属性设置为 true,从而配置 application 构建器。

不断发展的反应性

过去两年 Angular 的核心主题是不断发展我们的反应系统。

在版本 19 中,我们很高兴与大家分享一些新的辅助 API 以及我们在之前版本中引入的一些基本反应性 API 的稳定性,例如 inputs、outputs 和 view queries。

inputs、outputs 和 view queries API 的稳定性

在过去的一年里,我们观察了开发人员如何使用新的 inputs、outputs 和 view queries API,并且我们正在将它们升级到稳定!为了简化这些新 API 的采用,我们开发了原理图来转换您现有的 @Input@Output 和 view queries :

ng generate @angular/core:signal-input-migration
ng generate @angular/core:signal-queries-migration
ng generate @angular/core:output-migration

请注意,与传统 @Input 相比,signal inputs 是只读的,因此如果您要设置 input 值,则可能需要手动迁移应用程序的部分内容。

要一次运行所有这些迁移,您可以使用联合别名:

ng generate @angular/core:signals

您可以在我们的文档中阅读有关 inputsoutputsview queries 的更多信息。

通过语言服务实现代码现代化

为了让您能够轻松地将代码更新到最新的 API,我们引入了原理图和 Angular 语言服务之间的集成。

语言服务与原理图集成

当您将 Angular 语言服务和项目更新到 v19 时,您可以直接从代码编辑器将您的 inputs、view queries 等更新为最新的 API!

引入 linkedSignals API

开发人员反馈以及观察实际应用程序如何使用 Angular signals 时,我们看到了使用新原语更好地服务常见用例的机会。通常在 UI 中,需要可变状态来仍然跟踪一些更高级别的状态。

例如,选择 UI 具有“当前选择”状态,该状态会随着用户做出选择而变化,但如果选项列表发生变化,也需要重置。新的 linkedSignal 原语创建一个可写的 signals ,它捕获这种类型的依赖关系:

const options = signal(['apple', 'banana', 'fig']);

// Choice defaults to the first option, but can be changed.
const choice = linkedSignal(() => options()[0]);
console.log(choice()); // apple

choice.set('fig');
console.log(choice()); // fig

// When options change, choice resets to the new default value.
options.set(['peach', 'kiwi']);
console.log(choice()); // peach

linkedSignal 清楚地表达了 optionschoice 之间的关系,而无需求助于 effect 用法。新的 API 有 2 种形式:一种是简化的(此处介绍),另一种是高级的,开发人员可以访问 optionschoice 的先前值。它还具有高级 API,允许更复杂的逻辑,例如只要用户的选择存在于新的选项列表中,就保留该选择。

这个新的 API 是实验性的,所以请尝试一下并告诉我们您的想法!

介绍 Resource API

到目前为止,Angular 中的 signals 主要关注同步数据:在 signals、computed、 inputs、view queries 等中存储状态。在 Angular v19 中,我们通过引入新的实验 resource() API。resource 是参与 signals 图的异步依赖项。您可以将 resource 视为由三个部分组成:

  1. 请求函数,用 signals 表达要发出的确切请求。例如, user resource 可能会计算依赖于当前路由中的用户 ID 参数的请求。
  2. 加载器,当请求发生变化时执行异步操作,最终返回一个新值。
  3. 生成的 Resource 实例,它公开传达值(如果可用)以及 resource 当前状态(加载、已解决、出错等)的 signals。
@Component(...)
export class UserProfile {
  userId = input<number>();

  userService = inject(UserService);

  user = resource({
    request: userId,
    loader: async ({request: id}) => await userService.getUser(id),
  });
}

今天,我们将 resource() 作为独立的实验性 API提供,以便测试 API 并获得开发人员的早期反馈。随着时间的推移,我们期望逐渐将对 resource 的支持更深入地融入到 Angular 中(例如,作为解析器的一种形式融入到路由器中),作为应用程序中异步故事的关键部分。

因为现在许多 Angular 应用程序使用 RxJS 来获取数据,所以我们还将 rxResource 添加到 @angular/core/rxjs-interop 中,它从基于 Observable 的加载器创建 resource。

Effects API 的状态

在过去的几个版本中,我们一直保持开发者预览版的 effect,以观察开发者如何使用它们。根据您的反馈,在 v19 之前,我们对 effect 的执行时序进行了更改,以更好地服务于您的用例。您可以在我们的博客上阅读有关变更和 API 改进流程的更多信息。

作为新反应性 API 中的核心原语,我们希望花时间确保 effect 的语义正确。我们会将此 API 保留在开发者预览版中,以便在发现尚未考虑的用例时进行更改。

Zoneless 的状态

六个月前,我们向 Angular 引入了实验性 zoneless 支持。从那时起,我们一直在迭代 API 并对其进行增强——添加对服务器端渲染的支持并改善测试体验。我们还与 Google Fonts 团队合作,使其应用程序实现 zoneless 并评估开发人员体验。结果以及向 zoneless 过渡的简易性超出了我们的预期,但在将此 API 移至开发人员预览版之前,我们仍希望进行一些改进。

2025 年,我们将继续改进 zoneless。与此同时,请确保在应用程序引导程序中尝试一下,并让我们知道您的体验!创建无区域项目的最简单方法是使用 Angular CLI:

ng new [project-name] --experimental-zoneless

感谢 Angelo Parziale 对社区的贡献

在现有应用程序中,您可以使用实验性的 zoneless 提供程序:

bootstrapApplication(App, {
  providers: [
    provideExperimentalZonelessChangeDetection()
  ]
});

接下来,确保从 angular.jsonpolyfills 部分中删除 zone.js

推进 Angular Material 和 CDK

今年早些时候,我们发布了稳定版本的 Material 3,这使得我们的材质组件能够通过 Design Token 支持的强大 Sass 主题 API 更加可定制。在 v19 中,我们引入了主题 API 的增强功能,使您可以更轻松地自定义组件!

增强的主题 API

通过 Material 3,我们使您能够使用特定于组件的 mixin 创建自定义主题

@use '@angular/material' as mat;

@include mat.core();

$light-theme: mat.define-theme((
    color: (
      primary: mat.$violet-palette,
      tertiary: mat.$orange-palette,
      theme-type: light
    ),
    typography: Roboto,
    density: 0
  ));

html {
  // Apply the light theme by default
  @include mat.core-theme($light-theme);
  @include mat.button-theme($light-theme);
  @include mat.card-theme($light-theme);
  // and 27 more...
  ...
}

使用这种高度可定制的 API,您通常最终会为各个组件重复代码。为了简化自定义主题的创建,在 v19 中,我们启用了更具表现力的 API,允许您使用单个 mixin — mat.theme 来声明自定义主题:

@use '@angular/material' as mat;

html {
  @include mat.theme((
    color: (
      primary: mat.$violet-palette,
      tertiary: mat.$orange-palette,
      theme-type: light
    ),
    typography: Roboto,
    density: 0
  ));
}

覆盖组件样式

要自定义各个组件的样式,您可以使用我们在 Sass 中提供的新 overrides API:

@include mat.sidenav-overrides(
  (
    'content-background-color': purple,
    'container-divider-color': orange,
  )
);

上面的代码片段将分别将内容背景和内容分隔线颜色覆盖为 purpleorange,同时保留尊重您配置的应用程序主题的其余设计令牌的原始值。

二维拖放

为了使 Angular CDK 更加强大,我们在 CDK 中开发了对二维拖放的支持,这是 GitHub 上 311👍 的一个非常受欢迎的功能请求。

以下是如何使用 CDK 的此功能的快速片段:

<div
  cdkDropList
  cdkDropListOrientation="mixed"
  ...>
  @for (item of mixedTodo; track item) {
    <div cdkDrag>
      {{item}}
      <mat-icon cdkDragHandle svgIcon="dnd-move"></mat-icon>
    </div>
  }
</div>

结果会是这样的:

混合方向拖放演示

文档中了解更多信息。

支持选项卡的重新排序

我们最近发布的另一个功能请求是支持使用 Angular CDK 进行选项卡重新排序(24 👍)。使用此功能,您可以轻松地使选项卡可拖动,Google Cloud Console 团队立即通过 Angular 和 CDK 将其引入 BigQuery

BigQuery 可拖动标签

新的时间选择器组件

最受欢迎的功能请求之一是 Angular Material 的时间选择器组件,在 GitHub 上有超过 1.3k 个👍。

我们没有立即实施它,因为没有严格的规范,但考虑到需求,我们创建了一个符合您的要求和可访问性标准的设计,并在 v19 中发布!

时间选择器组件的基本使用

您现在就可以在 Angular 应用程序中使用它!在文档中查找更多信息。

还有更多!

除了我们在性能、反应性、开发人员体验和独立等主要主题上进行的重大改进之外,我们还进行了大量的质量改进,使构建 Angular 应用程序变得更加愉快!

报告独立组件中未使用的导入

报告独立组件中未使用的导入一直是最受欢迎的功能之一,有超过 150 个👍!

从 v19 开始,Angular CLI 将报告未使用的导入的警告,类似于下面的 gif:

此外,Angular 语言服务将突出显示此类未使用的导入,并提供直接在 IDE 或文本编辑器中自动删除它们的功能。

要取消此检查,您可以更新 tsconfig.json

{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "unusedStandaloneImports": "suppress"
      }
    }
  }
}

命令行环境变量声明

Angular CLI 存储库中请求最多的功能(超过 350 个👍)是能够在构建期间传递环境变量

从 v19 开始,您可以使用 --define 标志来实现此目的:

ng build --define "apiKey='$API_KEY'"
declare global {
  var apiKey: string;
}

await fetch(`/api/data?apiKey=${globalThis.apiKey}`);

模板中的局部变量

多年来,我们收到了数百个功能请求,要求在模板中引入局部变量声明语法 (443 👍)。不幸的是,多年来我们没有最佳的语法结构来做到这一点。

通过内置控制流和可延迟视图的新块语法,我们设计了一个解决方案,可以满足开发人员对本地模板变量声明的需求。

我们在开发者预览版中作为 Angular v18.1 的一部分提供了此功能。在观察开发人员如何使用这种新语法后,我们现在正在将其升级为稳定版本!

它可以与模板引用和异步管道完美配合:

<!-- Use with a template variable referencing an element -->
<input #name>

@let greeting = 'Hello ' + name.value;

<!-- Use with an async pipe -->
@let user = user$ | async;

随着最佳实践的不断发展,应用程序不断发展

与 Angular 和 Web 平台一起发展您的应用程序是我们的核心价值观之一。为了确保您的应用程序使用最新的 API 和最佳实践,我们进行了多项改进:

  • standalone 的默认值更改为 true,这可以简化所有独立组件、指令和管道的元数据。这是当您更新项目时 CLI 将自动运行的迁移
  • 通过运行将基于构造函数的依赖注入转换为注入函数调用的可选原理图:ng generate @angular/core:inject-migration
  • 可选原理图,可将急性加载的路由迁移到惰性加载的路由。感谢 Enea Jahollari 对社区的贡献
  • 将基于装饰器的 input、outputs 和 view queries 转换为最新 API 的可选原理图:
    • ng generate @angular/core:signal-input-migration
    • ng generate @angular/core:signal-queries-migration
    • ng generate @angular/core:output-migration
  • 支持 @angular/googe-maps 中的新聚合簇 API!您可以在 GitHub 上找到原始功能请求(26👍)。
  • 删除了 Protractor 构建器,以启用受支持的 e2e 测试工具的前进路径
  • 以及一个选择加入的迁移,将您的项目移动到使用 esbuild 和 vite 的应用程序构建器:ng update @angular/cli --name use-application-builder

非常感谢 Angular 社区

作为开发人员,我们正在专门为像您这样的其他开发人员构建产品。如果没有 Angular 社区的大力支持和贡献,我们就不会走到今天。你们每个人在塑造 Angular 的未来方面都发挥着至关重要的作用。

您的反馈、开源包以及积极参与聚会和会议帮助我们让 Angular 每天变得更好。您在 StackOverflow、Discord、Reddit、Telegram 等平台上分享的知识可以为全世界的开发者提供帮助。

我们邀请您在线或本地加入这个充满活力的社区。仅今年一年,全球就举办了十场 Angular 会议,分别来自比利时德国印度以色列意大利肯尼亚马其顿波兰塞尔维亚美国。这些活动是与其他开发人员联系、了解最新进展并分享您的专业知识的绝佳机会。

如果您组织的 Angular 会议不在我们的列表中,请通过 devrel@angular.io 告知我们,以便我们进行宣传。

我们还要感谢过去两个主要版本之间帮助我们塑造 v19 的所有 247 名贡献者。

让我们继续学习、成长并使用 Angular 构建令人惊奇的事物!

迈向 2025!

在过去的一年里,我们努力开发作为此版本一部分的所有功能。我们还与数百名开发者联系,收集您的反馈并了解我们如何在 2025 年为您提供最好的支持。

我们正在仔细检查我们的笔记和开发人员满意度调查的结果,以验证我们的假设。

不断出现的几个核心主题围绕着 Angular 创作体验的现代化和重新思考我们的单元测试建议。

我们计划明年初在这个领域进行彻底的研究,并与您分享我们的研究结果,以便在做出任何决定之前收集反馈。

与此同时,我们将继续完善我们的反应性 API,全面带来增量 DX 改进并不断发展 Angular 的性能,使您能够充满信心地构建 Web 应用程序!

感谢您帮助我们塑造 Angular 并迈向 2025 年! 🚀

原文:blog.angular.dev/meet-angula…