我们很高兴地宣布 Angular v14 的发布!从类型化表单和独立组件再到 Angular CDK(组件开发工具包)中的新原语,我们很高兴分享每个功能如何让 Angular 变得更强大。
自上次发布以来,我们完成了两个主要的评论请求 (RFC),这为整个 Angular 社区提供了一个机会,可以就提议的更改提供设计反馈。因此,我们的 Strictly Typed Reactive Forms RFC 关闭了我们的 #1 GitHub issue,我们的 Standalone APIs RFC 引入了一种更简单的方式来编写 Angular 应用程序。
我们还将 Angular 组织中存储库中的默认分支重命名为 main
,继续我们对包容性社区的承诺。
此外,此版本还包括社区成员直接贡献的许多功能和错误修复,从添加强类型路由器到更多可摇树的错误消息。我们很高兴地强调 RFC 和社区如何继续使 Angular 成为更好的默认开发人员体验!
使用独立组件简化 Angular
Angular 独立组件 旨在通过减少对 NgModules 的需求来简化 Angular 应用程序的创作。在 v14 中,独立组件处于开发者预览版中。它们已准备好在您的应用程序中用于探索和开发,但还不是一个稳定的 API,并且可能会在我们典型的向后兼容性模型之外发生变化。
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common'; // includes NgIf and TitleCasePipe
import { bootstrapApplication } from '@angular/platform-browser';
import { MatCardModule } from '@angular/material/card';
import { ImageComponent } from './app/image.component';
import { HighlightDirective } from './app/highlight.directive';
@Component({
selector: 'app-root',
standalone: true,
imports: [
ImageComponent, HighlightDirective, // import standalone Components, Directives and Pipes
CommonModule, MatCardModule // and NgModules
],
template: `
<mat-card *ngIf="url">
<app-image-component [url]="url"></app-image-component>
<h2 app-highlight>{{name | titlecase}}</h2>
</mat-card>
`
})
export class ExampleStandaloneComponent {
name = "emma";
url = "www.emma.org/image";
}
// Bootstrap a new Angular application using our `ExampleStandaloneComponent` as a root component.
bootstrapApplication(ExampleStandaloneComponent);
对于独立组件、指令和管道,您可以直接在 @Component()
中添加 standalone: true
标志,而无需 @NgModule()
。
探索一个新的 Stackblitz 演示应用程序,以了解有关如何使用独立组件构建 Angular 应用程序的更多信息。
在开发者预览版中,我们希望利用开源来确保 Angular 独立组件已做好充分准备,可以作为稳定的 API 发布。通过 ng generate component <name> --standalone
添加您的第一个独立组件,然后前往我们的 GitHub 存储库 ,在您开始探索时提供反馈。
在接下来的几个月中,我们将继续构建示意图(例如 ng new <app-name> --standalone
),并记录这种新的、简化的心智模型的用例和学习历程。请记住,在 开发者预览版 中,随着我们继续实施我们的设计,可能会发生变化。
您可以在 两个 RFC 和 公共设计审查 中阅读更多关于当前实现背后的设计思想。请务必在此处和 Twitter 上关注我们,以获取独立 API 的未来更新。
类型化的 Angular 表单
Angular v14 关闭了 Angular 的首要 GitHub Issue:为 Angular Reactive Forms 实现严格类型。
类型化的表单确保表单控件、组和数组中的值在整个 API 表面上都是类型安全的。这可以实现更安全的表单,尤其是对于深度嵌套的复杂案例。
const cat = new FormGroup({
name: new FormGroup({
first: new FormControl('Barb'),
last: new FormControl('Smith'),
}),
lives: new FormControl(9),
});
// Type-checking for forms values!
// TS Error: Property 'substring' does not exist on type 'number'.
let remainingLives = cat.value.lives.substring(1);
// Optional and required controls are enforced!
// TS Error: No overload matches this call.
cat.removeControl('lives');
// FormGroups are aware of their child controls.
// name.middle is never on cat
let catMiddleName = cat.get('name.middle');
此功能是 RFC 和 公共设计审查 的结果,它建立在 Angular 社区贡献者(包括 Sonu Kapoor、Netanel Basel 和 Cédric Exbrayat)之前的原型设计、工作和测试之上。
更新原理图允许增量迁移到类型表单,因此您可以逐步将类型添加到现有表单中,并具有完全的向后兼容性。ng update
将用无类型版本替换所有表单类(例如FormGroup
-> UntypedFormGroup
)。然后,您可以按照自己的节奏启用类型(例如UntypedFormGroup
-> FormGroup
)。
// v13 untyped form
const cat = new FormGroup({
name: new FormGroup(
first: new FormControl('Barb'),
last: new FormControl('Smith'),
),
lives: new FormControl(9)
});
// v14 untyped form after running `ng update`
const cat = new UntypedFormGroup({
name: new UntypedFormGroup(
first: new UntypedFormControl('Barb'),
last: new UntypedFormControl('Smith'),
),
lives: new UntypedFormControl(9)
});
为了利用新的类型支持,我们建议搜索 Untyped
表单控件的实例并尽可能迁移到新的类型化表单 API 接口。
// v14 partial typed form, migrating `UntypedFormGroup` -> `FormGroup`
const cat = new FormGroup({
name: new FormGroup(
first: new UntypedFormControl('Barb'),
last: new UntypedFormControl('Smith'),
),
lives: new UntypedFormControl(9)
});
我们建议新的应用程序使用 Form*
类,除非该类是有意无类型的(例如,一个 FormArray
同时具有数字和字符串)。在 文档 中了解更多信息。
简化的最佳实践
Angular v14 带来了内置功能,使开发人员能够构建高质量的应用程序,从路由到您的代码编辑器,从 angular.io 上的新更改检测指南开始。
简化的页面标题可访问性
另一个最佳实践是确保您的应用程序的页面标题唯一地传达页面的内容。v13.2 使用 Angular 路由器中新的 Route.title 属性简化了这一点。由于 Marko Stanimirović 的惊人社区贡献,添加 title
现在不需要额外的导入并且是强类型的。
const routes: Routes = [{
path: 'home',
component: HomeComponent
title: 'My App - Home' // <-- Page title
}, {
path: 'about',
component: AboutComponent,
title: 'My App - About Me' // <-- Page title
}];
您还可以提供自定义的 TitleStrategy
:
const routes: Routes = [{
path: 'home',
component: HomeComponent
}, {
path: 'about',
component: AboutComponent,
title: 'About Me' // <-- Page title
}];
@Injectable()
export class TemplatePageTitleStrategy extends TitleStrategy {
override updateTitle(routerState: RouterStateSnapshot) {
const title = this.buildTitle(routerState);
if (title !== undefined) {
document.title = `My App - ${title}`;
} else {
document.title = `My App - Home`;
};
};
@NgModule({
…
providers: [{provide: TitleStrategy, useClass: TemplatePageTitleStrategy}]
})
class MainModule {}
在这些示例中,导航到 /about
会将文档标题设置为 “My App — About Me”,而导航到 /home
会将文档标题设置为 “My App — Home”。
您可以在 Google I/O 2022 研讨会上了解有关此功能的更多信息,了解如何使用 Angular 进行无障碍构建。
扩展开发者诊断
新的扩展诊断提供了一个可扩展的框架,让您可以更深入地了解您的模板以及如何改进它们。诊断为您的模板提供编译时警告,并提供精确、可操作的建议,在运行时发现错误。
我们对它为开发人员在未来添加诊断功能引入的灵活框架感到兴奋。
在 v13.2 中,我们包含了内置的扩展诊断,以帮助开发人员捕获两个最常见的模板错误。
在双向数据绑定中捕获无效的 “Banana in a box” 错误
一个常见的开发人员语法错误是在双向绑定中翻转括号和括号,写成 ([])
而不是 [()]
。 由于()
看起来像香蕉,而 []
看起来像一个盒子,我们将其称为“盒子里的香蕉错误”,因为香蕉应该放在盒子里。
虽然这个错误在技术上是有效的语法,但我们的 CLI 可以认识到这很少是开发人员想要的。在 v13.2 版本中,我们在 CLI 和您的代码编辑器中引入了有关此错误的详细消息以及有关如何解决此问题的指导。
Warning: src/app/app.component.ts:7:25 - warning NG8101: In the two-way binding syntax the parentheses should be inside the brackets, ex. '[(fruit)]="favoriteFruit"'.
Find more at https://angular.io/guide/two-way-binding
7 <app-favorite-fruit ([fruit])="favoriteFruit"></app-favorite-fruit>
在不可为空的值上捕获空值合并
扩展诊断也会引发 Angular 模板中无用的无效合并运算符(??) 的错误。具体来说,当输入不是“可空的”时会引发此错误,这意味着它的类型不包括 null
或 undefined
。
扩展诊断在 ng build
、ng serve
、 和 Angular 语言服务中实时显示为警告。诊断可在 tsconfig.json
中配置,您可以在其中指定诊断应该是 warning
、error
还是 suppress
。
{
"angularCompilerOptions": {
"extendedDiagnostics": {
// The categories to use for specific diagnostics.
"checks": {
// Maps check name to its category.
"invalidBananaInBox": "error"
"nullishCoalescingNotNullable": "warning"
},
// The category to use for any diagnostics not listed in `checks` above.
"defaultCategory": "suppress"
},
...
},
...
}
在我们的 文档 和 扩展诊断博客文章 中了解有关扩展诊断的更多信息。
可摇树的错误消息
随着我们继续我们的 Angular 调试指南,Ramesh Thiruchelvam 的社区贡献添加了新的运行时错误代码。强大的错误代码使参考和查找有关如何调试错误的信息变得更加容易。
这允许构建优化器从生产包中摇树摇晃错误消息(长字符串),同时保留错误代码。
@Component({...})
class MyComponent {}
@Directive({...})
class MyDirective extends MyComponent {} // throws an error at runtime
// Before v14 the error is a string:
> Directives cannot inherit Components. Directive MyDirective is attempting to extend component MyComponent.
// Since v14 the error code makes this tree-shakeable:
> NG0903: Directives cannot inherit Components. Directive MyDirective is attempting to extend component MyComponent.
// v14 production bundles preserve the error code, tree-shaking strings and making the bundle XX smaller:
> NG0903
要调试生产错误,我们建议前往我们的 参考指南 并在开发环境中重现错误,以便查看完整的字符串。我们将继续增量重构现有错误,以在未来版本中利用这种新格式。
更多内置改进
v14 包括对最新 TypeScript 4.7 版本的支持,现在默认以 ES2020 为目标,这允许 CLI 交付更小的代码而无需降级。
此外,我们还想强调三个特性:
绑定到受保护的组件成员
在 v14 中,您现在可以直接从模板绑定到受保护的组件成员,这要归功于 Zack Elliott 的贡献!
@Component({
selector: 'my-component',
template: '{{ message }}', // Now compiles!
})
export class MyComponent {
protected message: string = 'Hello world';
}
这使您可以更好地控制可重用组件的公共 API 表面。
嵌入式视图中的可选注入器
v14 添加了对通过 ViewContainerRef.createEmbeddedView
和 TemplateRef.createEmbeddedView
创建嵌入式视图时传入可选注入器的支持。 注入器允许在特定模板中自定义依赖注入行为。
这为编写可重用组件和 Angular CDK 中的组件原语提供了更简洁的 API。
viewContainer.createEmbeddedView(templateRef, context, {
injector: injector,
})
NgModel OnPush
最后,Artur Androsovych 的社区贡献关闭了一个 热门问题,并确保 NgModel 更改反映在 OnPush 组件的 UI 中。
@Component({
selector: 'my-component',
template: `
<child [ngModel]="value"></child>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
class MyComponent {}
内置基元和工具
CDK 和工具改进为更强大的开发环境提供了构建块,从 CDK 菜单原语到 CLI 自动完成。
Angular CDK 中的新原语
Angular 的 组件开发工具包 提供了一整套用于构建 Angular 组件的工具。在 v14 中,我们将 CDK menu 和 dialog 升级为稳定版!
此版本包括新的 CDK 原语,可用于基于 WAI-ARIA 菜单和菜单栏设计模式 创建更易于访问的自定义组件。
<ul cdkTargetMenuAim cdkMenuBar>
<li cdkMenuItem
[cdkMenuTriggerFor]="file">
File
</li>
</ul>
<ng-template #file>
<ul cdkMenu cdkTargetMenuAim>
<li cdkMenuItem>Open</li>
</ul>
</ng-template>
hasHarness
和组件测试工具中的 getHarnessOrNull
v14 包括用于 HarnessLoader
检查线束是否存在以及返回线束实例(如果存在)的新方法。组件测试工具 继续提供一种灵活的方式来为您的组件编写更好的测试。
const loader = TestbedHarnessEnvironment
.loader(fixture);
const hasButton = await loader
.hasHarness(MatButtonHarness)
const buttonHarnessOrNull = await loader
.getHarnessOrNull(MatButtonHarness);
Angular CLI 增强功能
标准化的 CLI 参数解析意味着整个 Angular CLI 的一致性更高,现在每个标志都使用 --lower-skewer-case
格式。我们删除了已弃用的驼峰式大小写参数支持,并添加了对组合别名使用的支持。
好奇这是什么意思?运行 ng --help
更清晰的输出来解释您的选项。
自动完成
不小心打成 ng sevre
而不是 ng serve
的事情一直发生。拼写错误是命令行提示引发错误的最常见原因之一。为了解决这个问题,v14 的新 ng completion
功能引入了实时预输入自动完成功能!
为确保所有 Angular 开发人员都知道这一点,CLI 将提示您在 v14 中的第一个命令执行期间选择加入自动完成功能。您也可以手动运行 ng completion
,CLI 会自动为您设置。
分析
CLI 的 分析命令 允许您控制分析设置和打印分析信息。更详细的输出清楚地传达您的分析配置,并为我们的团队提供遥测数据,以告知我们的项目优先级。当你打开它时,它真的有很大帮助!
缓存
ng cache
提供了一种从命令行控制和打印缓存信息的方法。您可以启用、禁用或从磁盘中删除,并打印统计信息和信息。
Angular DevTools 可以离线使用,也可以在 Firefox 中使用
得益于 Keith Li 的社区贡献,Angular DevTools 调试扩展现在支持离线使用。对于 Firefox 用户,请在 Mozilla Addons 中找到该扩展。
实验性 ESM 应用程序构建
最后,v14 为 ng build
引入了一个实验性的基于 esbuild 的构建系统,用于编译纯 ESM 输出。
要在您的应用程序中尝试此操作,请更新您的 angular.json
中的浏览器构建器:
"builder": "@angular-devkit/build-angular:browser"
"builder": "@angular-devkit/build-angular:browser-esbuild"
下一步是什么
与此版本一起,我们更新了 公共路线图,以反映当前和未来团队项目和探索的状态。
您可以在 Angular Blog 和 Google I/O 2022 的 Angular State 中了解更多关于我们团队未来计划的信息。
感谢所有出色的开发人员每天构建、创新和激励我们 —— 我们对 Angular 的发展方向感到兴奋!
前往 update.angular.io 并在 @Angular 上发布关于您的 #ngUpdate 体验的推文!