[译] Angular 15 正式发布!

972 阅读10分钟

原文: blog.angular.io/angular-v15…

注:本人英语小学水平,翻译的不好见怪!英文过关的直接看原文吧!

在过去的一年中,我们移除了 Angular 的遗留编译器(ViewEngine)和渲染管道,这使得在过去几个月里一系列开发人员体验的改进被提升。Angular v15 是开发体验提升的顶点,它经过了数十次改进,可以带来更好的开发人员体验和性能。

image.png

独立 APIs 正式稳定!

在 v14 中,我们引入了新的独立(standalone) APIs,使开发人员能够在不使用 NgModules 的情况下构建应用程序。我们很高兴与大家分享这些 APIs 已经从开发预览版本中毕业,现在是稳定 API 的一部分。从现在开始,我们将通过语义版本控制独立 APIs 的发展。

作为确保独立 API 正式毕业的一部分,我们已经确保独立组件在 Angular 应用中工作,并且现在完全可以在HttpClientAngular ElementsRouterFroms等模块中工作。

独立 API 允许使用单个组件启动应用程序,代码如下:

import {bootstrapApplication} from '@angular/platform-browser';
import {ImageGridComponent} from'./image-grid';

@Component({
  standalone: true,
  selector: 'photo-gallery',
  imports: [ImageGridComponent],
  template: `
    … <image-grid [images]="imageList"></image-grid>
  `,
})
export class PhotoGalleryComponent {
  // component logic
}
bootstrapApplication(PhotoAppComponent);

Router 和 HttpClient 支持 tree-shakable 的独立 APIs

你可以使用新的 Router 独立 API 构建多路由应用程序,使用以下代码声明根路由:

export const appRoutes: Routes = [{
  path: 'lazy',
  loadChildren: () => import('./lazy/lazy.routes')
    .then(routes => routes.lazyRoutes)
}];

lazyRoutes 定义如下:

import {Routes} from '@angular/router';

import {LazyComponent} from './lazy.component';

export const lazyRoutes: Routes = [{path: '', component: LazyComponent}];

最终通过 bootstrapApplication 的 provideRouter 函数注册 appRoutes到根组件中:

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(appRoutes)
  ]
});

provideRouter API 的另一个优点是它是 tree-shakable (可摇树优化)的!打包器可以在构建时删除路由器未使用的功能。在我们使用新 API 进行的测试过程中,我们发现从打包结果中删除这些未使用的功能会使应用程序捆绑包代码的大小减少 11%。

Directive 组合 API

指令组合 API(directive composition API) 将代码复用提升到另一个高度!该功能的灵感来自 GitHub 上最受欢迎的 Issue feature request,该 Issue 要求提供向 Host Element 添加指令的功能。

指令组合 API 使开发人员能够使用指令增强 Host 元素能力,并为 Angular 提供了强大的代码复用策略,这只有通过我们的编译器才能实现,指令组合 API 仅适用于独立指令。

让我们看一个简单的示例:

@Component({
  selector: 'mat-menu',
  hostDirectives: [HasColor, {
    directive: CdkMenu,
    inputs: ['cdkMenuDisabled: disabled'],
    outputs: ['cdkMenuClosed: closed']
  }]
})
class MatMenu {}

在上面的代码片段中,我们用两个指令HasColorCdkMenu增强了 MatMenuMatMenu使用HasColors指令的所有输入、输出和相关逻辑,同时通过 inputs 和 outputs 参数控制仅使用CdkMenu的 cdkMenuDisabled 和 cdkMenuClosed 部分逻辑。

这种技术可能会让你想起某些编程语言中的多重继承特性,区别在于我们有一种解决名称冲突的机制,它适用于用户界面。

Image directive 现在可用!

我们在 image directive 文章中宣布了 Angular 图像指令的开发者预览,该指令是我们在 v14.2 中与 Chrome Aurora 合作开发的。

image.png

我们很高兴与大家分享它现在是稳定的!Land’s End 试验了这一功能,并在 lighthouse lab test 测试中观察到 LCP 改善了 75%。

15 版本还为 image 指令提供了一些新功能:

  • 自动生成 srcset :该指令通过生成 srcset 属性来确保请求适当大小的图像。这可以减少图像的下载时间。
  • 填充模式[experimental]:此模式使图像填充其父容器,从而消除了声明图像宽度和高度的要求。如果您不知道图像的大小,或者您想迁移 CSS 背景图像以使用该指令,这是一个方便的模式。

你可以在组件或 NgModule中直接使用独立的 NgOptimizedImage 指令:

import { NgOptimizedImage } from '@angular/common';

// Include it into the necessary NgModule
@NgModule({
  imports: [NgOptimizedImage],
})
class AppModule {}

// ... or a standalone Component
@Component({
  standalone: true
  imports: [NgOptimizedImage],
})
class MyStandaloneComponent {}

要在组件中使用它,只需将 Image 的 src属性替换为ngSrc,并确保为 LCP 图像指定priority属性。

你可以在我们的 documentation 中找到更多信息。

函数式路由守卫(functional router guards)

与可摇树优化的独立路由器 APIs 一起,我们致力于减少 guard 中的样板代码。让我们看一个示例,示例中我们定义了一个验证用户是否登录的守卫(guard):

@Injectable({ providedIn: 'root' })
export class MyGuardWithDependency implements CanActivate {
  constructor(private loginService: LoginService) {}

  canActivate() {
    return this.loginService.isLoggedIn();
  }
}

const route = {
  path: 'somePath',
  canActivate: [MyGuardWithDependency]
};

LoginService实现了大部分逻辑并且在守卫中仅仅是调用isLoggedIn()。尽管 guard 很简单,但是我们编写了很多样板代码 (boilerplate code)。

通过新的函数式路由守卫功能(functional router guards),你可以简化代码为:

const route = {
  path: 'admin',
  canActivate: [() => inject(LoginService).isLoggedIn()]
};

我们在 guard 声明中表达了整个 guard。函数式守卫也是可组合的——你可以创建类似工厂的函数,接受配置并返回 guard 或 resolver 函数。你可以在 GitHub 上找到一个连续运行 router guards 保护的示例。

路由自动打开默认导入 Router unwraps default imports

为了使 router 更简单并进一步减少样板代码,router 现在在延迟加载时自动打开默认导出。

假设你有如下的 LazyComponent:

 @Component({
  standalone: true,
  template: '...'
})
export default class LazyComponent { ... }

在进行此更改之前,要延迟加载独立组件,你必须:

{
  path: 'lazy',
  loadComponent: () => import('./lazy-file').then(m => m.LazyComponent),
}

现在,路由器将查找 default 导出,如果找到它,则自动使用它,这将简化路由声明:

{
  path: 'lazy',
  loadComponent: () => import('./lazy-file'),
}

这种使用必须要再 lazy-file 通过 export default class LazyComponent 默认导出。

更好的堆栈跟踪

我们从每年的开发者调查中获得了很多见解,因此我们要感谢你抽出时间分享您的想法!深入挖掘开发人员在调试经验方面面临的困难,我们发现错误消息可能需要一些改进。

image.png

调试 Angular 开发人员的难题

我们与 Chrome DevTools 合作解决了这个问题!让我们来看看你可能在 Angula r应用程序上使用的示例堆栈跟踪:

ERROR Error: Uncaught (in promise): Error
Error
    at app.component.ts:18:11
    at Generator.next (<anonymous>)
    at asyncGeneratorStep (asyncToGenerator.js:3:1)
    at _next (asyncToGenerator.js:25:1)
    at _ZoneDelegate.invoke (zone.js:372:26)
    at Object.onInvoke (core.mjs:26378:33)
    at _ZoneDelegate.invoke (zone.js:371:52)
    at Zone.run (zone.js:134:43)
    at zone.js:1275:36
    at _ZoneDelegate.invokeTask (zone.js:406:31)
    at resolvePromise (zone.js:1211:31)
    at zone.js:1118:17
    at zone.js:1134:33

此代码段有两个主要问题:

  • 只有一行与开发人员编写的代码相对应。其他一切都来自第三方依赖关系(Angular framework、Zone.js、RxJS)
  • 没有关于导致错误的用户交互的信息

Chrome DevTools 团队创建了一种机制,通过 Angular CLI 注释源映射来忽略来自node_modules的脚本。我们还合作了一个异步堆栈标记 API,该 API 允许我们将独立的、调度的异步任务连接到单个堆栈跟踪中。Jia Li 集成Zone.js 的异步堆栈标记 API,它允许我们提供链接堆栈跟踪。

这两项更改极大地改善了开发人员在 Chrome DevTools 中看到的堆栈跟踪:

ERROR Error: Uncaught (in promise): Error
Error
    at app.component.ts:18:11
    at fetch (async)  
    at (anonymous) (app.component.ts:4)
    at request (app.component.ts:4)
    at (anonymous) (app.component.ts:17)
    at submit (app.component.ts:15)
    at AppComponent_click_3_listener (app.component.html:4)

在这里,你可以跟踪从 AppComponent 中的按钮按下到错误的执行过程。您可以在这里阅读更多有关改进的信息。

发布基于 MDC 的组件稳定版

组件方面的更多改进

我们解决了排名第四的问题 - 滑块中支持范围选择 range selection support in the slider.。

要获得范围输入,请使用:

<mat-slider>
  <input matSliderStartThumb>
  <input matSliderEndThumb>
</mat-slider>

此外,所有组件现在都有一个 API 来自定义 density,这解决了另一个流行的 GitHub issue.。

现在,您可以通过自定义主题来指定所有组件的默认 density:

@use '@angular/material' as mat;

$theme: mat.define-light-theme((
  color: (
    primary: mat.define-palette(mat.$red-palette),
    accent: mat.define-palette(mat.$blue-palette),
  ),
  typography: mat.define-typography-config(),
  density: -2,
));

@include mat.all-component-themes($theme);

新版本的组件包括广泛的可访问性改进,包括更好的对比度、增加的触摸目标大小和改进的 ARIA 语义。

CDK Listbox

组件开发工具包(CDK)提供了一组用于构建 UI 组件的行为原语。在 v15 中,我们引入了另一个可以为你的昌吉自定义的原语——CDK Listbox:

image.png @angular/cdk/listbox 模块提供指令帮助我们自定义 listbox ,实现基于 WAI ARIA listbox 模式的列表框交互。

通过使用 @angular/cdk/listbox 你可以获取所有可访问性的期望行为,保留 bidi layout 支持, 键盘交互和焦点管理。 所有指令都将其关联的 ARIA roles 应用于其宿主元素。

实验性 esbuild 支持的改进

image.png

在 v14 中,我们宣布了对 ng build 中esbuild的实验支持,以加快构建时间并简化我们的管道。

v15中支持了实验性的 Sass, SVG 模块,文件替换(file replacement)以及 ng build --watch的支持,在angular.json中修改 builder 从:

"builder": "@angular-devkit/build-angular:browser"

改为:

"builder": "@angular-devkit/build-angular:browser-esbuild"

如果你实际环境中中遇到任何问题,请在 filing an issue 上提交问题,让我们知道。

语言服务中的自动导入

语言服务现在支持自动导入template中使用但尚未添加到独立组件或NgModule的组件。

image.png

CLI 改进

在 Angular CLI 中,我们引入了对独立稳定 API 的支持。现在,您可以通过ng g component --standalone生成一个新的独立组件。

我们还肩负着简化ng new输出的任务。作为第一步,我们通过删除test.tspolyfills.tsenvironments。现在可以直接以angular.jsonpolyfills指定填充。

"polyfills": [
  "zone.js"
]

为了进一步减少配置开销,我们现在使用 .browserlist来定义目标ECMAScript版本。

社区贡献亮点

我们很高兴与大家分享,自 v14 发布以来,我们收到了来自框架、组件和 CLI 的210多人的贡献!在本节中,我想重点介绍其中两个。

Provide an ability to configure default options for DatePipe

此功能允许你全局更改 DatePipe的默认格式配置,以下是 bootstrapApplication API 的示例:

bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: DATE_PIPE_DEFAULT_OPTIONS,
      useValue: { dateFormat: 'shortDate' }
    }
  ]
});

上面的配置将为应用程序中使用 DatePipe的所有位置启用 shortDate格式。

Add preload tag for priority images during SSR

为了确保尽可能快地加载优先级图像,Jay Bell 在图像指令中添加了一项功能,在使用 Angular Universal 时为它们添加 <link rel=“preload”>标记。

如果您已经启用了 image 指令,则无需执行任何操作。如果您已将图像指定为优先级,则指令将自动预加载它。

废弃(Deprecations)

主要版本使我们能够使框架朝着简单、更好的开发人员体验以及与 web 平台的一致性发展。

在分析了谷歌内的数千个项目后,我们发现很少有很少使用的模式,在大多数情况下被误用。因此,我们对所提供的选项providedIn: 'any'表示反对,除了框架内部的一些深奥的案例外,它的用途非常有限。

我们也反对 provided:NgModule。它没有广泛的用途,而且在大多数情况下,在您应该首选providedIn:"root"的情况下,它的使用是错误的。如果您应该真正将提供程序范围限定到特定的NgModule,请使用 NgModule.providers 代替。

随着 CSS 布局的不断发展,团队将停止发布 @angular/flex布局的新版本。我们将在明年继续提供安全和浏览器兼容性修复。您可以在我们的"现代CSS"系列的 first blog post 博客文章中了解更多关于这一点的信息。

对未来充满兴奋!

launch of Ivy in 2020 推出了许多全面的改进,你可以发现这些改进已经开始了, 可选 NgModules 就是一个很好的例子,它有助于减少初学者在关键学习过程中需要处理的概念,还支持高级功能,例如通过独立指令编写API。

接下来,我们将解决服务器端渲染管道和反应性的改进,同时全面提高生活质量!

迫不及待地想与你分享接下来的内容!

最后打一个广告

我们团队开源的 Angular 组件文档生成工具发布 2.0 版本,欢迎大家使用,详细参见:Docgeni 2.0 发布,开启自动化