六个月前,我们将独立 API从开发人员预览中升级,从而在 Angular 的简单性和开发人员体验方面达到了一个重要的里程碑。今天,我们很高兴地宣布,我们将继续 Angular Momentum,推出自 Angular 首次推出以来最大的版本;在反应性、服务器端渲染和工具方面取得巨大飞跃。所有这一切都伴随着跨功能请求的数十项生活质量改进,在 GitHub 上获得了 2,500 多个赞!
Angular v16 版本
作为 v16 版本的一部分,我们很高兴与大家分享一个全新的 Angular 反应模型的开发者预览,它显着改善了性能和开发者体验。
它完全向后兼容并可与当前系统互操作,并支持:
最初的GitHub 讨论获得了 682 条评论,从那时起我们分享了一系列 RFC,收到了超过 1,000 条评论!
在 v16 中,您可以找到一个新的信号库,它是@angular/core
RxJS 互操作包的一部分@angular/core/rxjs-interop
,框架中的完整信号集成将于今年晚些时候推出。
Angular 信号库允许你定义响应值并表达它们之间的依赖关系。您可以在相应的 RFC中了解有关库属性的更多信息。这是一个如何将它与 Angular 一起使用的简单示例:
@Component({
selector: 'my-app',
standalone: true,
template: `
{{ fullName() }} <button (click)="setName('John')">Click</button>
`,
})
export class App {
firstName = signal('Jane');
lastName = signal('Doe');
fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
constructor() {
effect(() => console.log('Name changed:', this.fullName()));
}
setName(newName: string) {
this.firstName.set(newName);
}
}
上面的代码片段创建了一个计算值fullName
,它取决于信号firstName
和lastName
。我们还声明了一个效果,每当我们更改它读取的任何信号的值时,回调都会执行——在本例中,fullName
这意味着它也传递地依赖于firstName
和lastName
。
当我们将 的值设置firstName
为“John”时,浏览器将登录到控制台:
"Name changed: John Doe"
@angular/core/rxjs-interop
作为 v16 版本的一部分,您将能够通过开发人员预览中的函数轻松地将信号“提升”到可观察对象!
以下是将信号转换为可观察信号的方法:
import { toObservable, signal } from '@angular/core/rxjs-interop';
@Component({...})
export class App {
count = signal(0);
count$ = toObservable(this.count);
ngOnInit() {
this.count$.subscribe(() => ...);
}
}
…这是一个示例,说明如何将可观察对象转换为信号以避免使用异步管道:
import {toSignal} from '@angular/core/rxjs-interop';
@Component({
template: `
<li *ngFor="let row of data()"> {{ row }} </li>
`
})
export class App {
dataService = inject(DataService);
data = toSignal(this.dataService.data$, []);
}
Angular 用户通常希望在相关主题完成时完成流。以下说明性模式非常常见:
destroyed$ = new ReplaySubject<void>(1);
data$ = http.get('...').pipe(takeUntil(this.destroyed$));
ngOnDestroy() {
this.destroyed$.next();
}
我们正在引入一个名为 的新 RxJS 运算符takeUntilDestroy
,它将此示例简化为以下内容:
data$ = http.get('…').pipe(takeUntilDestroyed());
默认情况下,此运算符将注入当前清理上下文。比如在组件中使用,会使用组件的生命周期。
takeUntilDestroy
当您想将 Observable 的生命周期与特定组件的生命周期联系起来时,它特别有用。
接下来,我们将研究基于信号的组件,这些组件具有一组简化的生命周期挂钩,以及另一种更简单的声明输入和输出的方法。我们还将致力于更完整的示例和文档集。
Angular 存储库中最受欢迎的问题之一是“建议:作为可观察输入”。几个月前,我们回应说我们希望支持这个用例,作为框架中更大努力的一部分。我们很高兴与大家分享,今年晚些时候我们将推出一项功能,支持基于信号的输入——您将能够通过互操作包将输入转换为可观察对象!
根据我们的年度开发人员调查,服务器端渲染是 Angular 改进的首要机会。在过去的几个月里,我们与Chrome Aurora 团队合作改进了水化和服务器端渲染的性能和 DX。今天我们很高兴分享完整应用程序无损水化的开发者预览!
在新的完整应用程序非破坏性水合作用中,Angular 不再从头开始重新渲染应用程序。相反,该框架在构建内部数据结构时查找现有的 DOM 节点,并将事件侦听器附加到这些节点。
好处是:
ngSkipHydration
在执行手动 DOM 操作的组件的模板中逐步采用水合作用和属性在早期测试中,我们看到Largest Contentful Paint的改进高达 45%,并具有完整的应用程序水合作用!
要开始使用它就像在您的中添加几行一样简单main.ts
:
import {
bootstrapApplication,
provideClientHydration,
} from '@angular/platform-browser';
...
bootstrapApplication(RootCmp, {
providers: [provideClientHydration()]
});
您可以在文档中找到有关其工作原理的更多详细信息。
作为 v16 版本的一部分,我们还更新了 Angular Universal 的 ng add schematics,使您能够使用独立 API 将服务器端渲染添加到项目中。我们还为内联样式引入了对更严格的内容安全策略的支持。
我们计划在这里做更多的事情,v16 中的工作只是垫脚石。在某些情况下,有机会延迟加载对页面不重要的 JavaScript,并在以后混合相关组件。这种技术被称为部分水化,我们接下来将对其进行探索。
自从 Qwik 从 Google 的封闭源代码框架 Wiz 中普及了可恢复性的想法后,我们收到了很多对 Angular 中此功能的请求。可恢复性肯定在我们的关注范围内,我们正在与 Wiz 团队密切合作探索这个领域。我们对其带来的对开发人员体验的限制持谨慎态度,评估不同的权衡,并会在我们取得进展时及时通知您。
您可以在“ Angular 中服务器端渲染的下一步是什么”中阅读更多关于我们未来计划的信息。
Angular 是数百万开发人员用于许多关键任务应用程序的框架,我们认真对待重大变化。几年前我们开始探索独立的 API ,在 2022 年我们在开发者预览版下发布了它们。现在,经过一年多的收集反馈和迭代 API,我们希望鼓励更广泛的采用!
为了支持开发人员将他们的应用程序转换为独立 API,我们开发了迁移示意图和独立迁移指南。进入项目目录后运行:
ng generate @angular/core:standalone
原理图将转换您的代码,删除不必要的NgModules
类,并最终更改项目的引导程序以使用独立的 API。
作为 Angular v16 的一部分,您可以从一开始就独立创建新项目!要尝试独立原理图的开发人员预览,请确保您使用的是 Angular CLI v16 并运行:
ng new --standalone
您将获得一个更简单的项目输出,没有任何NgModules
. 此外,项目中的所有生成器都将生成独立的指令、组件和管道!
在独立 API 首次发布后,我们从开发人员那里得知您希望能够使用新bootstrapApplication
API配置 Zone.js。
我们为此添加了一个选项provideZoneChangeDetection
:
bootstrapApplication ( App , { providers : [ provideZoneChangeDetection ({ eventCoalescing : true })] });
现在,让我们分享 Angular CLI 和语言服务的一些功能亮点。
一年多以前,我们宣布我们正在努力为 Angular CLI 中的 esbuild 提供实验性支持,以使您的构建速度更快。今天,我们很高兴地与大家分享,在 v16 中,我们基于 esbuild 的构建系统进入了开发者预览版!早期测试表明,冷生产构建的改进超过 72%。
基于 esbuild 的构建器的开发者预览版
我们ng serve
现在使用 Vite 作为开发服务器,而 esbuild 为您的开发和生产构建提供支持!
我们要强调的是 Angular CLI 完全依赖 Vite 作为开发服务器。为了支持选择器匹配,Angular 编译器需要维护组件之间的依赖图,这需要与 Vite 不同的编译模型。
你可以通过更新你的:来尝试 Vite + esbuild angular.json
:
... "architect" : { "build" : { /* 添加 esbuild 后缀 */ "builder" : "@angular-devkit/build-angular:browser-esbuild" , ...
接下来我们将解决对 i18n 的支持,然后再从开发人员预览中毕业这个项目。
根据 Angular 和更广泛的 JavaScript 社区中的开发人员调查,Jest是最受欢迎的测试框架和测试运行器之一。我们收到了大量支持 Jest 的请求,由于不需要真正的浏览器,因此复杂性降低了。
今天,我们很高兴地宣布我们将引入实验性的 Jest 支持。在未来的版本中,我们还将现有的Karma项目移至Web Test Runner,以继续支持基于浏览器的单元测试。对于大多数开发人员来说,这将是一个空操作。
npm install jest --save-dev
您可以通过安装 Jest并更新文件来在新项目中试验 Jest angular.json
:
{
"projects": {
"my-app": {
"architect": {
"test": {
"builder": "@angular-devkit/build-angular:jest",
"options": {
"tsConfig": "tsconfig.spec.json",
"polyfills": ["zone.js", "zone.js/testing"]
}
}
}
}
}
}
您可以在我们最近的博客文章中了解有关我们未来单元测试策略的更多信息。
您有多少次在模板中使用组件或管道从 CLI 或语言服务中获取您实际上没有导入相应实现的错误?我打赌很多次!
语言服务现在允许自动导入组件和管道。
Angular 语言服务自动导入
Gif 显示了 VSCode 中 Angular 语言服务的自动导入功能
在 v16 中,我们还启用了对 TypeScript 5.0 的支持,支持ECMAScript装饰器,消除了 ngcc 的开销,在独立应用程序中添加了对service workers和app shell的支持,扩展了CLI 中的 CSP 支持,等等!
除了我们关注的大型计划外,我们还致力于带来高度要求的功能。
自从我们在 2016 年引入 Angular 以来,如果您不为特定输入指定值,就不可能出现编译时错误。由于 Angular 编译器在构建时执行检查,因此更改在运行时增加了零开销。多年来,开发人员一直 要求 此功能 ,我们得到了一个强烈的迹象,表明这将非常方便!
在 v16 中,您现在可以根据需要标记输入:
@Component (...) export class App { @Input ({ required : true }) title : string = '' ; }
路由器的开发人员体验一直在快速发展。GitHub 上一个流行的功能请求是要求能够将路由参数绑定到相应组件的输入。我们很高兴地告诉大家,此功能现已作为 v16 版本的一部分提供!
现在您可以将以下数据传递给路由组件的输入:
以下是如何从路由解析器访问数据的示例:
const routes = [ { path : 'about' , loadComponent : import ( './about' ), resolve : { contact : () => getContact () } } ]; @Component (...) export class About { // 将“contact”的值传递给contact input @Input () contact?: string ; }
Angular 在组件样式的 DOM 中包含的内联样式元素违反了默认的style-src
内容安全策略 (CSP)。要解决这个问题,它们应该包含一个nonce
属性,或者服务器应该在 CSP 标头中包含样式内容的散列。尽管在谷歌我们没有找到针对此漏洞的有意义的攻击向量,但许多公司执行严格的 CSP,导致对 Angular 存储库的功能请求的流行。
nonce
在 Angular v16 中,我们实现了一个跨越框架、Universal、CDK、Material 和 CLI 的新功能,它允许您为 Angular 内联的组件的样式指定一个属性。有两种方法可以指定随机数:使用属性ngCspNonce
或通过CSP_NONCE
注入令牌。
如果您有权访问可以将两者添加到标头和构建响应时的ngCspNonce
服务器端模板,则该属性很有用。nonceindex.html
<html>
<body>
<app ngCspNonce="{% nonce %}"></app>
</body>
</html>
另一种指定随机数的方法是通过CSP_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
}]
});
Angular 的生命周期钩子提供了强大的功能来插入应用程序执行的不同时刻。多年来的一个机会是实现更高的灵活性,例如,提供对OnDestroy 作为 observable 的访问。
在 v16 中,我们使 OnDestroy 可注入,从而实现开发人员一直要求的灵活性。这个新功能允许您注入DestroyRef
对应的组件、指令、服务或管道——并注册onDestroy
生命周期挂钩。可以DestroyRef
在注入上下文中的任何地方注入,包括组件外部——在这种情况下,onDestroy
当相应的注入器被销毁时,钩子就会被执行:
import { Injectable, DestroyRef } from '@angular/core';
@Injectable(...)
export class AppService {
destroyRef = inject(DestroyRef);
destroy() {
this.destroyRef.onDestroy(() => /* cleanup */ );
}
}
我们最近实现的一项非常受欢迎的功能允许您为 Angular 模板中的组件使用自闭合标签。这是一个小的开发人员体验改进,可以节省您的输入时间!
现在你可以替换:
< super-duper-long-component-name [ prop ]= "someVar" > </ super-duper-long-component-name >
有了这个:
< super-duper-long-component-name [ prop ]= "someVar" />
在过去的几个季度中,我们与 Google 的 Material Design 团队密切合作,为 Web 和 Angular Material 提供参考 Material 3 实现。我们在 2022 年发布的基于 Web 的 MDC 组件为这项工作奠定了基础。
作为下一步,我们正努力在今年晚些时候推出一个基于令牌的富有表现力的主题 API,以实现 Angular 材质组件的更高定制化。
提醒一下,我们将在 v17 中删除遗留的、非基于 MDC 的组件。请确保您按照我们的迁移指南迁移到最新版本。
遵循 Google 的使命,Angular 可让您为所有人构建 Web 应用程序!这就是为什么我们不断投资以提高Angular CDK 和 Material 组件的可访问性。
我们要强调的社区引入的两个功能是:
provideServiceWorker
service workerNgModules
超过 175 人在 GitHub 上为 v16 做出了贡献,还有数千人通过博客文章、演讲、播客、视频、对反应性 RFC 的评论等做出了贡献。
我们非常感谢所有帮助我们使这个版本变得特别的人。
版本 16 是明年 Angular 的反应性和服务器端渲染未来改进的垫脚石。我们将通过在开发人员体验和性能方面进行创新来推动 Web 向前发展,同时让您能够为每个人构建!
你可以成为 Angular Momentum 的一员,通过在即将到来的 RFC、调查或社交媒体中分享你的想法来帮助我们塑造框架的未来。
感谢您成为 Angular 社区的一员。我们迫不及待地想让您尝试这些功能!