前言
在最近的项目中,团队从 Angular 15 升级到 Angular 19,中间了跨越了挺多个版本的,这些版本中的变化,特别是语法上的调整,这就需要对一些新语句调整上进行重新学习。
Signal
官网介绍
signal是对一个值的包装,当该值发生变化时,它会通知感兴趣的用户。信号可以包含任何值,从基本类型到复杂的数据结构。
你可以通过调用getter函数来读取信号的值,这允许Angular跟踪信号在哪里被使用。
信号可以是可写的,也可以是只读的。
Angular 中的 Signals 是一个响应式值,允许开发者以受控的方式更改值,并且跟踪值
的变化。
信号的类型
1. 可写信号(Writable Signal)
定义:可写信号是可以直接更新其中值的信号,通过使用.set() 或 .update() 方法,进行修改
创建方式: 使用signal() 函数创建一个可写信号,并设置初始化值
示例:
const count = signal(0); // 创建可写信号,初始值为 0
console.log(count()); // 读取信号的值,输出:0
count.set(5); // 设置新的值为 5
console.log(count()); // 读取信号的值,输出:5
count.update(value => value + 1); // 将信号值加 1
console.log(count()); // 读取信号的值,输出:6
2. 计算信号(Computed Signal)
定义:计算信号是只读信号,通过其他信号进行计算,计算信号的值不能被修改,依靠全其他间接修改
创建方式:使用 computed() 函数来创建计算信号,并指定一个计算函数,这个函数返回信号的派生值
示例:
const count = signal(0); // 创建可写信号,初始值为 0
const doubleCount = computed(() => count() * 2); // 创建计算信号,计算 count 的两倍
console.log(doubleCount()); // 输出:0,计算值为 count 的两倍
count.set(5); // 修改 count 的值
console.log(doubleCount()); // 输出:10,计算值会自动更新为 count 的两倍
从这个案例我们可以看到当 count(可写信号)发生变化时,Angular 会检测到这个变化,并触发相关的计算信号(如 doubleCount)重新计算其值。
3.Effects函数
effect 函数用于创建 副作用(effects),这也是新特性,主要的作用是允许你自动执行一个回调函数,当你检测的信号值变更,这个回调就会进行执行
示例:
import { signal, effect } from '@angular/core';
const count = signal(0);
effect(() => {
console.log('The count has changed to:', count()); // 每当 count 发生变化时,这条消息会被打印
});
// 修改 count,触发 effect 执行
count.set(5); // 输出:The count has changed to: 5
count.set(10); // 输出:The count has changed to: 10
从上面我可以看到,当我们创建一个effect(),这个函数接受一个回调函数,每次信号的值发生变更的时候,effect会重新执行这个回调函数。
注意点
effect 函数通常是与 signal 一起使用的。effect 用来创建副作用,它会监听和响应一个或多个 signal 的变化。当 signal 的值发生变化时,effect就会检测到变更之后执行相应的操作
RxJS与Angular Signal的互操作
Signal 能通过与RXJS进行结合,简化和增加应用的响应式特性
@angular/rxjs-interop 包提供了一些 API,帮助你将 RxJS 和 Angular 信号进行集成
创建信号从 RxJS Observable 使用 toSignal
使用ToSignal函数用于把RXJS Observable 创建一个对象
@Component({
selector: 'app-root',
imports: [CommonModule],
standalone: true,
template: `{{ counter() }}`,
styleUrl: './app.component.css'
})
export class AppComponent {
counterObservable = interval(1000);
counter = toSignal(this.counterObservable, {initialValue: 0});
}
这里会进行批量从 0 一直开始增加
默认情况下,当创建 Observable 的组件或服务被销毁时, toSignal会自动取消订阅 Observable。
也可以自己进行手动取消订阅Observable
使用toObservable从信号创建 RxJS Observale
可以使用Signal 来存储查询条件,并通过 toObservable 将其转换为 Observable,每当Signal查询条件变化时,会发起新的 HTTP 请求并显示结果。
@Component({
selector: 'app-root',
imports: [CommonModule],
standalone: true,
template: `{{ counter() }}`,
styleUrl: './app.component.css'
})
export class AppComponent {
query: Signal<string> = signal('');
query$: any; // 用于将 Signal 转换成 Observable
results$: any;
constructor(private http: HttpClient) {
// 将 Signal 转换为 Observable
this.query$ = toObservable(this.query);
// 根据查询信号值发起 HTTP 请求
this.results$ = this.query$.pipe(
switchMap(query => this.http.get('/search?q=' + query))
);
}
// 更新查询条件
updateQuery(newQuery: string) {
this.query.set(newQuery); // 设置新的查询条件
}
}
为什么使用Signal
使用 原始值(传统的 Angular 变更检测)
在传统的angular中,我们通常是使用普通的组件属性来存储状态,并依靠Angular的变更检测来更新
import { Component } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div>
<p>当前计数:{{ count }}</p>
<button (click)="increment()">增加</button>
</div>
`
})
export class CounterComponent {
count = 0;
increment() {
this.count++;
}
}
变更检测机制(原始值)
当我们按下按钮的时候,count的值就会发生变化。
angular就会触发变更检测,检测组件的数据是否有变化。
变更检测原理:
Angular 使用 Zone.js 来自动检测异步操作(例如点击事件)的变化,并通知 Angular 启动变更检测。
默认情况下,当我们的组件中某个值发生了变化触发了变化检测,那么Angular会从上往下检查所有的组件。
使用 Signal(新机制)
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div>
<p>当前计数:{{ count() }}</p>
<button (click)="increment()">增加</button>
</div>
`
})
export class CounterComponent {
// 使用 signal 定义一个响应式的 count 状态
count = signal(0);
increment() {
this.count.update(count => count + 1);
}
}
变更检测机制
count 是一个 Signal,而不是普通的属性。Signal 是一个响应式对象,可以直接跟踪它的状态变化。
使用 count() 获取当前值,count.update() 方法用于更新状态。
变更检测原理:
Signal 允许精确地跟踪和更新状态。当信号的值发生变化时,只有与该信号绑定的部分会被更新。
Angular 会根据信号的变化自动更新视图,而不需要进行全局的变更检测。
总结:
1.没有 Signal 的传统 Angular:
比如你有一个计数器,点击按钮时会增加一个数字。
即使你只改变了一个数字,Angular 会遍历整个组件,检查是否需要更新页面中的所有绑定。
2.有 Signal 的 Angular:
计数器用 Signal 来追踪数字变化。
当数字变化时,只有与这个数字绑定的部分会被更新。Angular 不需要检查页面中的其他内容。
引入 @for 和 @if
在Angular17版本的时候,模版就应该有了比较大的变化,特别是引人了@for和@if语句的引入,使模版变得简洁,相比之前的版本使用ngfor和ngIf,新语法也比较容器理解
Angular 15 中的写法
<!-- Angular 15: 使用 *ngFor 和 *ngIf -->
<div *ngIf="users.length > 0; else noUsers">
<div *ngFor="let user of users">
{{ user.name }}
</div>
</div>
<ng-template #noUsers>
<p>暂无用户</p>
</ng-template>
*
ngIf 和 *
ngFor 是常用的结构性指令,用来根据条件渲染内容。
<ng-template [ngIf]="users.length > 0">
<ng-template ngFor let-user [ngForOf]="users">
<div>{{ user.name }}</div>
</ng-template>
</ng-template>
<ng-template #noUsers>
<p>暂无用户</p>
</ng-template>
这里ngIf和ngFor是简单的指令。然后每个ng-template都会生成一个“视图”。ng-template 是一个隐藏的模板,它的内容只有在条件满足时才会被显示出来,而 ngIf 和 ngFor 就是控制这些模板何时显示的工具。
*ngIf 的工作原理:
当你使用 ngIf 时,Angular 会根据 ngIf 表达式的值来决定是否在 真实 DOM 中创建或销毁该元素的视图。比如,当 *ngIf 条件为 true 时,Angular 会动态创建该元素的视图,并将其插入到真实 DOM 中。
当 *ngIf 条件为 false 时,Angular 会从真实 DOM 中移除该视图,而不仅仅是隐藏它。这就意味着页面上不再显示相关元素,也不会占用任何空间。
Angular 17 及之后的写法
从Angular 17 开始,官方引入了@for和@if语法,这种新写法少了对ng-template得嵌套,
Angular 有个概念叫做ViewContainer。ViewContainer是Angular的一个内部API。一个ViewContainer就像一个盒子,您可以在其中插入/删除子视图。
<!-- Angular 17+:使用 @for 和 @if -->
@if (users.length > 0) {
@for (let user of users; track user.id) {
<p>{{ user.name }}</p>
}
} @else {
<p>暂无用户</p>
}
类似JavaScript的语法
*
ngIf, *
ngFor:由于需要嵌套 ng-template,这会让模板的结构变得较为复杂,尤其当逻辑较多时,阅读起来可能会感到不太直观。
使用@if, @for 新语法更接近 JavaScript 语法,学习起来比较友好。
性能提升
*
ngIf, *
ngFor:每次使用 *
ngIf 和 *
ngFor 时,Angular 会为每个条件渲染或循环渲染生成一个 ng-template,这在复杂的应用中可能影响性能,特别是在处理大量数据时。
@if, @for: 减少了不必要的 ng-template 实例化,提高了性能,减少了不必要的渲染,这种方法比直接操作 DOM 要更高效,因为 Angular 只会关注那些真正改变的部分,而不会每次都重绘整个页面
增量 DOM
增量 DOM 的概念就是在每次数据或视图变化时,通过指令来动态生成和更新 DOM,并与当前的真实 DOM 进行差异比较。
组件编译为指令:增量 DOM 将每个组件编译成一组指令,这些指令定义了如何创建和更新 DOM。指令是增量 DOM 的核心,它们控制着 DOM 的渲染和更新过程。
生成新的 DOM 树:每当组件的输入(数据)发生变化时,增量 DOM 会使用这些指令重新生成一个新的 DOM 片段,表示新的视图。
与原始 DOM 比对:新的 DOM 片段会与原来的真实 DOM 进行比对,找出差异。这是增量 DOM 和虚拟 DOM 的一个主要区别,虚拟 DOM 会先生成完整的 DOM 树再与真实 DOM 比对,而增量 DOM 直接生成新的 DOM,并对差异部分进行更新。
最小化 DOM 更新:与传统的虚拟 DOM 比较不同,增量 DOM 只更新需要改变的部分,而不需要完全重新渲染整个 DOM 结构。这样可以提高性能,减少不必要的渲染操作。
虚拟 DOM
虚拟 DOM 是前端框架(如 React、Vue)中常用的一种高效更新视图的技术,虚拟 DOM 是一个以 JavaScript 对象形式存在的 DOM 树的抽象表示。
工作流程
创建虚拟 DOM:根据初始状态,创建一个虚拟 DOM 树(JavaScript 对象)。
状态更新:当状态发生变化时,生成新的虚拟 DOM 树。
当用户 UI 发生变化时,将整个用户 UI 渲染到虚拟 DOM 中。
Diff 算法:比较新旧两个虚拟 DOM 树,找出变化部分。
更新真实 DOM:将变化应用到真实 DOM,仅更新需要变动的部分。
总结
总体来说Angular到17版本变化挺大,开始偏向vue的方式发展,总归到底还是为了解决大项目大导致的性能问题
参考地址https://angular.dev/overview
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。