Angular 21 - 有什么新功能? - Angular.love
又一个 Angular 主要更新发布了,是时候来详细了解一下了。让我们分析一下哪些内容发生了变化,以及如何在日常工作中使用它们。
Zoneless 成为默认选项
正如之前的文章中提到的,从 Angular 20.2 开始,zoneless 变更检测已经达到了稳定状态。在此基础上,Angular 21 将 zoneless 作为新应用程序的默认选项,并且已经准备好了帮助现有 Angular 项目迁移到 zoneless 的脚手架工具。
让我们来探索一个称为
信号表单 (Signal Forms)
让我们创建一个非常简单的响应式表单:
export class App implements OnInit {
private readonly _fb = inject(FormBuilder);
protected form!: PersonForm;
ngOnInit() {
this.initForm();
}
protected onSubmit() {
if (this.form.valid) {
console.log('Form submitted:', this.form.value);
}
}
private initForm() {
this.form = this._fb.group({
name: this._fb.nonNullable.control('',
[
Validators.required,
Validators.minLength(3)
]
),
surname: this._fb.nonNullable.control('',
[
Validators.required,
Validators.maxLength(10)
]
),
telephoneNumber: this._fb.control(
null,
Validators.required
),
});
}
接下来,我们可以在模板中使用它:
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div>
<label>
姓名:
<input type="text" formControlName="name" />
</label>
<@if(form.controls.name.invalid && form.controls.name.touched) {
<p>姓名是必填项</p>
}
</div>
<div>
<label>
姓氏:
<input type="text" formControlName="surname" />
</label>
<@if(form.controls.surname.invalid && form.controls.surname.touched)
{
<p>姓氏是必填项</p>
}
</div>
<div>
<label>
电话号码:
<input type="number" formControlName="telephoneNumber"/>
</label>
<@if(form.controls.telephoneNumber.invalid &&
form.controls.telephoneNumber.touched
) {
<p>电话号码是必填项</p>
}
</div>
<button type="submit" [disabled]="form.invalid">提交</button>
</form>
<pre>{{ form.value | json }}</pre>
`,
如我们所见,这是一个非常简单的示例。我们初始化了一个基本的响应式表单,包含三个控件:姓名、姓氏和电话号码。在模板中,我们检查用户是否与控件进行了交互,如果是,则检查验证条件是否满足。如果不满足,我们会显示一个简单的错误消息。让我们看看在新版本的 Angular 中如何修改和改进这一点。
首先,让我们更新组件类:
protected readonly person = signal<PersonForm>({
name: '',
surname: '',
telephoneNumber: null
})
protected readonly personForm = form(this.person);
如你所见,我们创建了一个简单的信号,它作为我们新信号表单的模型。我们还删除了 OnInit 方法——它不再需要。[form()] 函数是最新版本 Angular 中引入的新 API。请记住,当我们更新 personForm 中的值时,表单模型中的值也会自动更新:
changePersonName(value: string) {
this.personForm.name().value.set(value);
console.log(this.person()); // {name: 'John', surname: '', telephoneNumber: null}
}
让我们继续看模板,看看我们如何在其中使用表单。在更新表单之前,请确保字段指令——负责将表单字段绑定到 UI 组件——已正确导入。完成后,我们可以按如下方式更新表单:
template: `
<form (ngSubmit)="onSubmit()">
<div>
<label>
姓名:
<input [field]="personForm.name" type="text"/>
</label>
</div>
<div>
<label>
姓氏:
<input [field]="personForm.surname" type="text"/>
</label>
</div>
<div>
<label>
电话号码:
<input [field]="personForm.telephoneNumber" type="number" />
</label>
</div>
<button type="submit" [disabled]="personForm().invalid()">提交</button>
</form>
<pre>{{ personForm().value() | json }}</pre>
`,
就像上面的示例一样简单。你可能注意到错误处理已被删除——我们故意这样做,以演示机制是如何改变的。我们现在可以通过迭代可能的错误并显示存在的错误来简单地处理错误。
protected readonly personForm = form(this.person, (path) => {
required(path.name);
required(path.surname);
minLength(path.name, 3);
maxLength(path.surname, 40);
});
这就是我们如何显示错误:
<div>
<label>
姓名:
<input [field]="personForm.name" type="text" />
</label>
<@for(err of personForm.name().errors(); track $index) {
<@if(err.kind === 'required') {
<p>姓名是必填项</p>
}
}
</div>
你可能想知道 personForm 内部实际发生了什么。实际上这里没有魔法——我们的验证逻辑只是被包装在一个 schema 函数中。我们还使用新的验证工具,例如 required()。我们需要做的就是为每个验证函数提供正确的路径,这可以从传递给 schema 函数的参数中获取。
请记住,信号表单仍处于实验阶段,因此在稳定版本发布之前,API 和行为可能会发生变化。
新 UI 库 – Angular Aria
可访问性在我们的日常开发工作中变得越来越重要。这些不仅仅是推荐的最佳实践
考虑到可访问性日益增长的重要性,Angular 团队引入了一个新的 UI 库:Angular Aria。这为开发人员提供了另一个强大的工具,用于构建可访问的用户界面,与 Angular Material 和 CDK 一起。请注意,该库目前处于开发者预览阶段。
你可以通过在终端中运行以下命令轻松地将库添加到项目中:
npm install @angular/aria
你可以在这里找到有关库的更多详细信息:https://blog.angular.dev/announcing-angular-v21-57946c34f14b
SimpleChanges 现在是一个泛型
在最新的 Angular 21 版本中,SimpleChanges 已更新为泛型类型。这意味着我们现在可以显式定义每个 @Input() 属性携带的数据类型,这允许 TypeScript 在 ngOnChanges 生命周期钩子内部强制执行更强的类型检查。以前,SimpleChange 对 previousValue 和 currentValue 使用 any 类型,这意味着开发人员对传递的值的类型没有编译时保证。你可以从下面看到它现在是如何工作的。
export interface User {
userName: string;
age: number;
}
@Component({
//…//
})
export class App {
@Input({required: true}) userName!: string;
@Input({required: true}) age!: number;
ngOnChanges(changes: SimpleChanges<User>) {
if (changes.age) {
const newAge = changes.age.currentValue;
const oldAge = changes.age.previousValue;
const diff = newAge - oldAge;
console.log(`年龄增加了 ${diff} 岁`);
}
}
}
HttpClient 默认提供
随着最新版本 Angular 的引入,我们不再需要在应用程序中提供 HttpClient。它将默认提供。因此,在创建配置对象时,如果愿意,可以跳过提供 HttpClient。
// import { provideHttpClient } from `@angular/common`
export const appConfig: AppConfig = {
providers: [
...anotherProviders,
// provideHttpClient()
]
}
NgClass 指令到样式绑定——新脚手架工具已上线
正如我们所知,不推荐使用 ngClass;但是,你仍然可以在我们的应用程序中使用它。避免使用此指令是一个好习惯。为了使我们的工作更轻松、更快速,Angular 团队准备了一个迁移脚手架,可自动将所有 ngClass 用法转换为 class 绑定。
以下是迁移前的样子:
@Component({
//…//
imports: [NgClass],
template: `
<button [ngClass]="{
'isNew': isNew()
}">点击我</button> //迁移前
`,
})
export class App {
protected readonly isNew = signal(true);
}
迁移后:
@Component({
//…//
// imports: [NgClass] - 不再需要
template: `
<button [class]="{
'isNew': isNew()
}">点击我</button> //迁移后
`,
})
export class App {
protected readonly isNew = signal(true);
}
如你所见,我们不再需要导入 NgClass,这使得我们的包更小,代码更简洁——因此更易读。此脚手架工具通过以下命令运行:
ng generate @angular/core:ngclass-to-class
正在计划下一次升级或只是想保持最新?我们制作了从 Angular 14 到最新版本的完整概述,以帮助开发人员和决策者了解变化内容及其重要性。免费下载《Angular 演进终极指南》
NgStyle 指令到样式绑定的迁移——新脚手架工具
与 ngClass 指令迁移类似,已准备了一个脚手架来将 ngStyle 指令迁移到样式绑定。
和以前一样,这里是一个迁移前后的示例:
@Component({
//…//
imports: [NgStyle],
template: `
<button [ngStyle]="{
'border-color': borderColor(),
}">点击我</button> //迁移前...



最新评论