Angular 16 的新特性
自动路线参数映射
必填项
新的 DestroyRef 注入器
Vite 作为开发服务器
非破坏性水合作用
结论
自 Angular 14 发布以来,每个版本都带来了许多新功能,这是不争的事实,Angular 16 也不例外。
我们已经听说Angular集成了信号模式一段时间了,但Angular 16带来的远不止这一新特性。
它们是什么?如何实施它们?
你想知道的一切都在这里!
信号
信号模式从 Solid Js 库一开始就存在,并且基于推送/拉取模式。
顾名思义,pull允许您检索值,而push允许您设置新值或修改值。
总之,信号总是有值的,并且该值的获取总是同步进行的。
在 Angular 的 16 版本中,将集成模式基础 API,这将使 Angular 在处理变更检测方面获得更高的性能。
问题是为什么?
在 Angular 应用程序启动时,该框架会覆盖浏览器的一些底层 API,例如 addEventListener 函数。
此覆盖是通过 Zone JS 库完成的。Zone 是 Angular 在底层使用的一个库,它通过监听来触发变更检测。
- DOM 事件,例如点击、鼠标悬停……
- HTTP 请求
- setTimeout、setInterval 函数
因此,当应用程序中的某些内容发生变化时,系统会执行变更检测,并刷新页面。
与任何使用客户端渲染框架创建的应用程序一样,您的应用程序是通过组件树实现的。
在 Angular 中,每个组件都关联着一个变更检测器。因此,如果某个子组件发生更改,整个组件树就会被重新评估,而无需考虑各个组件之间的依赖关系。我们称之为“脏检查”。
即使您的应用程序的变更检测处于OnPush模式,变更检测周期也会遍历整个树,与默认模式不同的是,没有变更依赖项的 OnPush组件将不会被重新评估。
因此,Angular 中变更检测机制显然并不理想,而 Signal 的集成正是为了解决这个问题。
借助 Signal,变更检测的粒度可以细化到信号变量的级别。这样,只有当信号发生变化时,组件级别才会进行变更检测,而无需遍历整个组件树,也无需使用 ZoneJS。未来,ZoneJS 可以作为可选组件。
在 Angular 中创建信号很简单,只需调用信号函数并传入一个初始值即可。
const counter = signal(0) // create a signal with initial value to 0;
console.log(this.counter()) // display 0
signal 函数返回一个 WritableSignal,允许我们修改信号或为其设置新值。
/**
Set function let to set a signal's value with a new value. Usefull if you need to change the data structure when the new value is not dependent of the old one.
Notify all dependents.
**/
set(value: T): void;
/**
Update function let to update the signal's value if the update depends on the precedent value.
In other words this function help if you want to update the value of the signals in a immutable way.
Notify all dependents.
**/
update(updateFn: (value: T) => T): void;
/**
Mutate function let to update the signal's value by mutating it in place.
In other words this function is useful for making internal change to the signal's value without changing its internal identity.
Notify all dependents.
**/
mutate(mutatorFn: (value: T) => void): void;
/**
Return a readonly signal.
**/
asReadonly(): Signal<T>;
计算
计算出的信号可以用于从其他依赖信号创建衍生信号。
const person = signal<{ firstname: string; lastname: string}>({ firstname: 'John', lastname: 'Doe'})
// Automatically updates when person() change;
const presentation = computed(() => ${person().firstname}${person().lastname}`;
只有当其中一个因变量信号发生变化时,才会重新评估计算得到的信号。
计算结果可以替代著名的管道。
影响
这些效果使得执行读取零个或多个信号值的副作用操作成为可能,并且只要其中任何一个信号发生变化,就会自动安排重新运行。
该API的设计如下:
function effect(
effectFn: (onCleanup: (fn: () => void) => void) => void,
options?: CreateEffectOptions
): EffectRef;
一个具体的例子如下
query = signal('');
users = signal([]);
effect(async (onCleanup) => {
const controller = new AbortController();
const response = await fetch('/users?query=' + query())
users.set(await response.json());
onCleanup(() => controller.abort())
})
信号是Angular中一种原始的响应式系统。这意味着它们既可以在组件内部使用,也可以在组件外部使用,例如在服务中。
自动路线参数映射
让我们设想这样一种路由:
export const routes: Routes = [
{ path: 'search:/id',
component: SearchComponent,
resolve: { searchDetails: searchResolverFn }
}
]
在 Angular 16 之前,必须注入 ActivatedRoute 服务才能检索 url 参数以及查询参数或与此 url 关联的数据。
@Component({...})
export class SearchComponent {
readonly #activateRoute = inject(ActivatedRoute);
readonly id$ = this.#activatedRoute.paramMap(map(params => params.get('id');
readonly data$ = this.#activatedRoute.data.(map(({ searchDetails }) => searchDetails)
}
使用 Angular 16,不再需要注入 ActivatedRoute 服务来检索各种路由参数,因为这些参数可以直接绑定到组件输入。
对于使用模块系统的应用程序,要激活此功能,可以在 RouterModule 选项中找到激活选项。
RouterModule.forRoot(routes, { bindComponentInputs: true })
对于独立应用程序,这是一个需要调用的函数。
provideRoutes(routes, withComponentInputBinding());
功能激活后,组件就变得非常简单了。
@Component({...})
export class SearchComponent {
@Input() id!: string;
@Input() searchDetails!: SearchDetails
}
必填项
社区期待已久的功能是能够将某些输入设为必填项。
此前,人们已经采用了几种变通方法来实现这一目标:
- 如果变量未定义,则在 NgOnInit 生命周期中引发错误。
- 修改组件的选择器,使其包含所有必填的输入项。
这两种方案各有优缺点。
从版本 16 开始,将输入设置为必填项将是一个简单的配置对象,该对象将传递给输入注释的元数据。
@Input({ required: true }) name!: string;
新的 DestroyRef 注入器
Angular v16 引入了一个名为 DestroyRef 的新提供程序,它允许为特定的生命周期范围注册销毁回调。此功能适用于组件、指令、管道、嵌入式视图和 EnvironmentInjector 实例。
这种用法非常简单。
@Component({...})
export class AppComponent {
constructor() {
inject(DestroyRef).onDestroy(() => {
// Writte your cleanup logic
})
}
}
有了这个新的提供程序,Angular 允许我们共享一些经典的清理逻辑,例如取消订阅我们的可观察对象。
export function destroyed() {
const replaySubject = new replaySubject(1);
inject(DestroyRef).onDestroy(() => {
replaySubject.next(true);
replaySubject.complete();
});
return <T>() => takeUntil<T>(replaySubject.asObservable());
}
@Component({...})
export class AppComponent {
readonly #untilDestroyed = untilDestroyed();
ngOnInit() {
interval(1000)
.pipe(this.#untilDestroyed())
.subscribe(console.log);
}
}
Vite 作为开发服务器
随着 Angular 14 的发布,引入了使用新的 JavaScript 打包工具 EsBuild 的可能性。
这个新的打包工具速度非常快,可以将构建时间缩短约 40%。主要问题是,这项新功能和性能提升只能用于构建,而不能用于开发(开发服务器)。
在 Angular 的下一个版本中,由于 Vite 的存在,Esbuild 也可以在开发过程中使用。
要在 angular.json 文件中启用此功能,请按如下方式更新构建器:
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser-esbuild",
"options": { ... }
请注意,此功能仍处于实验阶段。
非破坏性水合作用
Angular 允许您借助 Angular Universal 创建服务器端渲染应用程序。
问题在于,我们通常会发现自己创建的应用程序效率不高,这主要是由于代码水合(hydration)机制造成的。
此前,代码水合机制是破坏性的。也就是说,整个页面会被销毁,然后在浏览器恢复并执行 JavaScript 代码后,重新构建并渲染。
好消息是,API 已经重写,支持部分水合。也就是说,一旦 HTML 加载完毕且 DOM 完成,整个结构就会被遍历,以附加不同的事件监听器并重新创建应用程序的状态,从而使应用程序具有响应性,但无需重新渲染。
结论
Angular 16 版本无疑带来了一些很棒的新功能。其中一些功能仍处于实验阶段,例如信号机制或类似 Vite 的开发服务器。
总之,这些新特性无疑会改变我们编写 Angular 应用程序的方式,使它们减少样板代码,更加优化,并以更简单的方式为集成 vitest 或 playwright 等新技术打开大门。
Angular 16 版本尚未发布,本文中描述的某些 API 可能仍会发生变化。尽管如此,本文仍能让您大致了解 Angular 下一个版本的功能。
文章来源:https://dev.to/this-is-angular/whats-new-in-angular-16-375b