Angular 18+ 高级教程 – 盘点 Angular v14 到 v18 的重大改变
前言
我在 <初识 Angular> 文章里有提到 Angular 目前的断层问题。

大部分的 Angular 用户都停留在 v9.0 版本。
Why everyone stay v9.0?
v9.0 是一个里程碑版本,Angular 从 v4.0 稳定版推出后,好几年都没有什么动静,直到 v9.0 推出了 Ivy rendering engine。
本以为 v9.0 以后 Angular 会大爆发,结果迎来的是 Angular 团队搞内讧,又...好几年没有动静。直到 v14.0 Angular 突然就...变了。
Angular 团队大换血之后,有了新方向,原本那批人的特色 “不爱创新,爱 follow 标准,爱小题大" 现在已不复存在,新一批人的特色是 "爱 follow 市场,爱新用户,爱借其它团队的力"。
这也是为什么从 v14 以后大家都感觉 Angular 好像不是 Angular 了。
是福是祸,现在还很难说,所以大部分人都宁愿停留在 v9.0 继续观望,反正 v9.0 到 v18 也没有推出什么新功能。
v14 到 v18 那么多改变,都离不开 "爱 follow 市场,爱新用户" 原则,所以老一批的用户看待这些改变的第一反应都是嗤之以鼻。
为什么写这篇?
很多人在静观其变,但同时心里又有些焦虑,本篇就是要带你体会一下 v14 后的 Angular,让你决定是要转投 Vue 3, React 19, Svelte 5 还是继续留在 Angular 阵营。
The Concept Behind the Change
Angular 长久以来一直有一个诟病 -- 学习门槛太高。
这绝对是千真万确的事情。如果有人告诉你学习 Angular 很简单,上手很快,那你要先问清楚,是他教会了很多人快速掌握 Angular,还是只是他自己快速掌握了 Angular。
这是两个完全不同的概念,他自己掌握或许只是因为他比一般人悟性高而已,千万不能以偏概全,不然会误人子弟的。
为什么 Angular 学习门槛会这么高呢?太多太多原因了,一句话总结就是 "不爱创新,爱 follow 标准,爱小题大" 再加一个 "不在乎用户"。
所以,新团队的第一个方向就是降低 Angular 的学习门槛。那要怎样降低呢?
简单丫,把一堆概念去掉,不就变得简单了吗。
去除 NgModule
所谓的去除其实是 optional 的意思,好好的功能怎么可能删掉嘛,只是不逼着你学,不逼你用而已。
NgModule 适合用来批量管理组件,但如果组件少的话,就会变成 1 个 NgModule 只管理 1 个组件,这就很多此一举啊,一个有什么好管理的?
去除 Decorator
Decorator 简直是乱七八杂的东西,草案了这么久,后来又大改。虽然现在是定案了,但生态也没起来 (esbuild 就不支持 Decorator)
去除 Zone.js
Zone.js 本来是不错的,但很遗憾,最终没能进入 ECMA。那 monkey patching 的东西谁还敢用呢?
去除 RxJS
RxJS 是很好用,但是要学啊。必须改成 optional。
去除 Structural Directive
结构型指令的语法叫微语法 (Syntax Reference)。
微语法是挺灵活的,也支持扩展,但学习成本也不少。
而但绝大部分时候,我们只有在使用原生结构型指令 *ngIf, *ngFor 时才用到微语法。
这就很没必要学啊。
好,以上几个就是 Angular v14 以后改变的方向。未来还会不会出现 ”去除 TypeScript“ 或 "去除 OOP",那我就不晓得了。
Optional NgModule の Standalone Component
Angular v14 以前,组件一定要依附在 NgModule 上,然后 NgModule import 另一个 NgModule 让组件可以相互使用,一个 NgModule 管理一批组件。
站管理角度,分组批量管理组件是正确的。但对于小项目而言,很多时候 1 个 NgModule 里面就只 declare 了一个组件,因为就没有那么多组件丫。
这种情况 NgModule 就显得很多余,为了写而写,为了管理而管理,这是不对的。
Angular v14 以后,组件可以单独存在,不需要再依附 NgModule。组件也可以直接 import 另一个组件达到相互使用的结果。NgModule 变成 optional 了。
@Component({
selector: 'app-test',
standalone: true, // 在 @Component 声明 standalone: true 就可以了
templateUrl: './test.component.html',
styleUrl: './test.component.scss'
})
export class TestComponent {}
直接 import 就能用了。
@Component({
selector: 'app-root',
standalone: true,
imports: [TestComponent], // 直接 import 组件, no more NgModule
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {}
使用
<app-test />
注:v16 支持了 self-closing-tag 写法
效果

App 组件变成 Standalone Component 后,bootstrap 的方法就不同了
bootstrapApplication(
AppComponent,
{ providers: [] }
)
.catch((err) => console.error(err));
Provider 不写在 NgModule.providers 而是写在 bootstrapApplication 函数的参数。
想深入理解 NgModule 请看这篇 Angular 18+ 高级教程 – NgModule。
Optional Decorator の inject, input, output, viewChildren, contentChildren
提醒:Angular 要 optional 很多概念,这个过程是循序渐进的,这里要说的 Optional Decorator 不是说整个项目完完全全不写 Decorator,目前只是部分地方可以 optional 而已。
inject 函数
下面是 Dependency Injection 依赖注入 Decorator 的写法
export class TestComponent {
constructor(
@SkipSelf() @Optional() @Inject(CONFIG_TOKEN) config: Config,
@Attribute('value') value: string
) {
console.log(config);
console.log(value);
}
}
下面是 v14 后,用 inject 函数替代 Decorator 的写法。
export class TestComponent {
constructor() {
const config = inject(CONFIG_TOKEN, { optional: true, skipSelf: true });
const value = inject(new HostAttributeToken('value'));
}
}
想深入理解 Dependancy Injection 请看这两篇 Dependency Injection 和 NodeInjector。
input, output 函数
下面是组件 input, output Decorator 的写法
export class TestComponent {
@Input({ required: true, transform: numberAttribute })
age!: number;
@Output('timeout')
timeoutEventEmitter = new EventEmitter();
}
注:input required 是 v16 的功能
下面是 v17 后,用 input 和 output 函数替代 Decorator 的写法。
export class TestComponent {
age = input.required({ transform: numberAttribute });
timeoutEventEmitter = output({ alias: 'timeout' })
}
v17 的写法显然没有以前整齐了 (无法一眼分辨哪些 property 是 input, output),
但没办法,为了去除 Decorator...只能牺牲整齐度了。
另外一个重点,input 函数不仅仅替代了 Decorator,它还引入了 Signal 概念。
input 函数的返回类型是 Signal 对象。
想深入理解 Signal 请看这篇 Angular 18+ 高级教程 – Signals。
viewChildren, contentChildren 函数
下面是组件 query element Decorator 的写法
export class TestComponent {
@ViewChildren('item', { read: ElementRef })
itemElementRefQueryList!: QueryList<ElementRef<HTMLElement>>;
@ViewChild('item', { read: ElementRef })
itemElementRef!: ElementRef<HTMLElement>;
@ContentChildren('product', { read: ElementRef })
productRefQueryList!: QueryList<ElementRef<HTMLElement>>;
@ViewChild('product', { read: ElementRef })
productElementRef!: ElementRef<HTMLElement>;
}
下面是 v17 后,用 viewChildren 和 contentChildren 函数替代 Decorator 的写法。
export class TestComponent {
itemElementRefs = viewChildren('item', { read: ElementRef });
itemElementRef = viewChild.required('item', { read: ElementRef });
productElementRefs = contentChildren('product', { read: ElementRef });
productElementRef = contentChild.required('product', { read: ElementRef });
}
它们返回的类似也是 Signal 哦。
想深入理解 Query Elements 请看这篇 Component 组件 の Query Elements。
Optional Zone.js
Zone.js 是用来 detect ViewModel change 的,没有了它要怎样 detect change 呢?
答案是 Signal。
在 app.config.ts 用 provideExperimentalZonelessChangeDetection 取代原本的 provideZoneChangeDetection (Zone.js)。
import { provideExperimentalZonelessChangeDetection, type ApplicationConfig } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
// provideZoneChangeDetection({ eventCoalescing: true }) // 这是 Zone.js,注释掉它
provideExperimentalZonelessChangeDetection()
],
};
接着在 angular.json 也把 zone.js 注释掉

使用 signal 对象作为属性值
export class AppComponent {
value = signal(0);
startTimer() {
setInterval(() => {
this.value.update(v => v + 1);
}, 500);
}
}
App Template
<p>{{ value() }}</p>
<button (click)="startTimer()" >start timer</button>
效果

模板会自动 tracking Signal 对象值的变化,每当值改变就会 refresh LView。
如果要用 Zone.js 实现相同的效果,我们需要手动 ChangeDetectorRef.markForCheck。
export class AppComponent {
constructor(private changeDetectorRef: ChangeDetectorRef) {}
value = 0;
startTimer() {
window.setInterval(() => {
this.value++;
this.changeDetectorRef.markForCheck();
}, 500);
}
}
或者使用 RxJS + AsyncPipe
export class AppComponent {
constructor() {}
value = new BehaviorSubject(0);
startTimer() {
setInterval(() => {
this.value.next(this.value.value + 1);
}, 500);
}
}
<p>{{ value | async }}</p>
<button (click)="startTimer()" >start timer</button>
你更喜欢哪一种写法呢?我猜应该是...Svelte 5 吧?
想深入理解 Change Detection 请看这篇 Angular 18+ 高级教程 – Change Detection。
Optional RxJS
Signal 很像 RxJS 的 BehaviorSubject,而 BehaviorSubject 也是一种 Observable,所以在一些情况下,Signal 确实可以替代 RxJS,使得 RxJS 成为 optional。
下面是 RxJS 的写法
const firstNameBS = new BehaviorSubject('Derrick');
const lastNameBS = new BehaviorSubject('lastName');
const fullName$ = combineLatest([firstNameBS, lastNameBS]).pipe(
map(([firstName, lastName]) => `${firstName} ${lastName}`)
);
fullName$.subscribe(fullName => console.log('fullName', fullName));
setTimeout(() => {
firstNameBS.next('Alex');
lastNameBS.next('Lee');
}, 1000);
下面是 Signal 的写法
export class AppComponent {
constructor() {
const firstName = signal('Derrick');
const lastName = signal('Yam');
const fullName = computed(() => firstName() + ' ' + lastName());
effect(() => console.log('fullName', fullName()))
setTimeout(() => {
firstName.set('Alex');
lastName.set('Lee');
}, 1000);
}
}
是不是挺像的?写法上差不多,但实际运行的逻辑还是有一些不同哦。
下面是一个把 RxJS Observable 转换成 Signal 的例子
import { toSignal } from '@angular/core/rxjs-interop';
constructor() {
const number$ = new Observable(subscriber => {
let index = 0;
const intervalId = window.setInterval(() => subscriber.next(index++), 1000);
return () => {
window.clearInterval(intervalId);
}
});
const number = toSignal(number$);
effect(() => console.log(number())); // 会一直 log
}
toSignal 会 subscribe number$ 然后一直接收新值,effect 可以监听每一次值得变化。
Optional RxJS 目前还只停留在 planning 中。Angular built-in 的 Router, HttpClient, ReactiveForms 依然是返回 RxJS Observable。
想深入理解 Signal 请看这篇 Angular 18+ 高级教程 – Signals。
Optional Structural Directive Syntax Reference (结构型指令微语法)
下面是一个常见的结构型指令微语法
<h1 *ngIf="user$ | async as user; else loading">{{ user.firstName }}</h1>
<ng-template #loading>loading...</ng-template>
这还是优化过的版本哦,没有优化更丑,请看
<ng-template [ngIf]="user$ | async" let-user="ngIf" [ngIfElse]="loading">
<h1>{{ user.firstName }}</h1>
</ng-template>
<ng-template #loading>loading...</ng-template>
里面涉及了很多知识:AsyncPipe, as syntax, else syntax, Template Variable, ng-template, ng-template as ng-container 等等。
下面是 v17 后,用 Control Flow 替代结构型指令的写法。
@if (user$ | async; as user) {
<h1>{{ user.firstName }}</h1>
}
@else {
loading...
}
是什么干净了很多?
换上 Signal 版本
@if (user(); as user) {
<h1>{{ user.firstName }}</h1>
}
@else {
loading...
}
export class AppComponent {
user = toSignal(new BehaviorSubject({ firstName: 'Derrick' }).pipe(delay(2000)));
}
效果

Control Flow 可以替代 *ngIf, *ngFor, *ngSwitch 指令。
想深入理解 "结构型指令微语法" 请看这篇 Structural Directive (结构型指令) & Syntax Reference (微语法)
想深入理解 Control Flow 请看这篇 Component 组件 の Control Flow。
其它小改动
以上这些都是 Angular v14 - v18 为降低学习门槛所做出的改动。
当然 v14 - v18 远远不只改动了这些,还加了许多新功能,这里我讲几个比较常用到的,有兴趣的可以点击链接查看:
Typed Forms (v14)
setInput (v14)
DestroyRef (v16)
takeUntilDestroyed (v16)
afterNextRender (v16)
我有必要升级改写法吗?
看到那么多改动,大家一定心里很焦虑,有种 AngularJS 被抛弃的感觉。
但其实呢...大家根本不用瞎焦虑。
这些改动都只是表层而已,底层 Ivy rendering engine 压根就没动过。
要知道,Angular 现在是在做减法,而不是加法。
我们跟着升级是很安全的,breaking changes 不多。
好,升级可以,那写法要改吗?
告诉你一个秘密,Angular Material 源码里:
一堆的 @Inject, @Input
一堆的 NgZone
一行 Signal 也没有
所以大家根本不用急,版本升级是必要的,有写新代码就用新的写法就可以了。
确实会有一些功能 (比如 Directive Composition API) 只有新写法支持,但是这种情况很少的,
要记得,他们底层 Ivy rendering engine 压根就没动过 (不然你以为 Angular 团队真的突飞猛进??)
所以,我非常鼓励大家升级 Angular 版本,至于写法嘛...不急
我自己体会了几个月,有一些新写法会比之前好,但有一些只是半径八两,我建议大家可以先学起来,有机会就写一写体会一下,总之不着急。
想了解新写法请看这篇 -- Coding Style Guide 编码风格
Next Big Thing の Wiz
v19 估计 Signal 就要完结了。
Angular 下一个 Big Thing 是结合 Wiz,相关资讯:Angular and Wiz Are Better Together
科普一下,Wiz 是 Google 内部没有开源的框架。
Google 有很多 Web Application (比如:Google Ads, Analytics, Search Console, Tag Manager, Cloud 等等)
另外还有一些 Website (比如:Youtube, Search, Google Photos 等等)
它们可以分成两大派系,一个注重交互,一个注重速度。
目前重交互的通常使用 Angular,重速度的则使用 Wiz。
Wiz 的作者是大名鼎鼎的 Malte Ubl,现任 Vercel CTO,没错,就是那个 React 的 Next.js。
所以你大概可以遇见 Angular 之后会往 Next.js 的方向走,加强 SSR 和 SSG。
v17 推出的 Control Flow 除了有 @if, @for, @switch 之外还有一个叫 @defer,这个是全新的概念,它的灵感就是来源之 Wiz。
所以大家不需要太焦虑,跟着 Angular 慢慢走就可以了,记住,现在这群人的特色:"爱 follow 市场,爱新用户,爱借其它团队的力" 再加上 "有 planning"。
目录
下一篇 Angular 18+ 高级教程 – Coding Style Guide 编码风格
想查看目录,请移步 Angular 18+ 高级教程 – 目录
喜欢请点推荐,若发现教程内容以新版脱节请评论通知我。happy coding
Angular 18+ 高级教程 – 盘点 Angular v14 到 v18 的重大改变的更多相关文章
- Angular CLI 使用教程指南参考
Angular CLI 使用教程指南参考 Angular CLI 现在虽然可以正常使用但仍然处于测试阶段. Angular CLI 依赖 Node 4 和 NPM 3 或更高版本. 安装 要安装Ang ...
- angular.js高级程序设计书本开头配置环境出错,谁能给解答一下
server.jsvar connect=require('connect');serveStatic=require('serve-static');var app=connect();app.us ...
- NgRx/Store 4 + Angular 5使用教程
这篇文章将会示范如何使用NgRx/Store 4和Angular5.@ngrx/store是基于RxJS的状态管理库,其灵感来源于Redux.在NgRx中,状态是由一个包含action和reducer ...
- ReactiveX 学习笔记(18)使用 RxJS + Angular 调用 REST API
JSON : Placeholder JSON : Placeholder (https://jsonplaceholder.typicode.com/) 是一个用于测试的 REST API 网站. ...
- angular源码分析:angular中的依赖注入式如何实现的
一.准备 angular的源码一份,我这里使用的是v1.4.7.源码的获取,请参考我另一篇博文:angular源码分析:angular源代码的获取与编译环境安装 二.什么是依赖注入 据我所知,依赖注入 ...
- Siki_Unity_2-9_C#高级教程(未完)
Unity 2-9 C#高级教程 任务1:字符串和正则表达式任务1-1&1-2:字符串类string System.String类(string为别名) 注:string创建的字符串是不可变的 ...
- [Angular] ngx-formly (AKA angular-formly for Angular latest version)
In our dynamic forms lessons we obviously didn’t account for all the various edge cases you might co ...
- Pandas之:Pandas高级教程以铁达尼号真实数据为例
Pandas之:Pandas高级教程以铁达尼号真实数据为例 目录 简介 读写文件 DF的选择 选择列数据 选择行数据 同时选择行和列 使用plots作图 使用现有的列创建新的列 进行统计 DF重组 简 ...
- angular源码分析:angular中脏活累活的承担者之$interpolate
一.首先抛出两个问题 问题一:在angular中我们绑定数据最基本的方式是用两个大括号将$scope的变量包裹起来,那么如果想将大括号换成其他什么符号,比如换成[{与}],可不可以呢,如果可以在哪里配 ...
- angular源码分析:angular中入境检察官$sce
一.ng-bing-html指令问题 需求:我需要将一个变量$scope.x = '<a href="http://www.cnblogs.com/web2-developer/&qu ...
随机推荐
- 第二部分:关键技术领域的开源实践【内网穿透FRP】
FRP简介 FRP(Fast Reverse Proxy)作为一种高性能的内网穿透工具,支持 TCP.UDP.HTTP.HTTPS 等多种协议.可以将内网服务以安全.便捷的方式通过具有公网IP节点(云 ...
- B+树要点梳理
B+树重要操作 中间节点 中间节点的key,与其对应的指针的原则是,小于key的元素在其指针指向的节点中 中间节点的key可以看成是右斜着排放的,即小于等于key的节点由key对应的指针指定,最有一个 ...
- 输入Javac提示不是内部或外部命令
先去百度搜索"jdk下载"下载最新版jdk,并安装,安装目录不用去更改,直接默认就好,下载完了之后,双击打开安装,jdk安装完成后,会接着安装jre包,(jre和jdk是配对的,不 ...
- Microsoft宣布将在开发人员会议上专注于.NET Aspire
2024年7月15日微软宣布,其开发执行团队将在下个月的开发者大会上聚焦于使用 .NET Aspire 的云原生开发,以及结合人工智能的"现代 SQL"在 Microsoft Fa ...
- 对比python学julia(第二章)--(第三节)玫瑰曲线—数学之美
3.1.问题描述 在数学世界中有一些美丽的曲线图形,有螺旋线.摆线.双纽线.蔓叶线且.心脏线.渐开线.玫瑰曲线.蝴蝶曲线-- 这些形状各异.简有繁别的数学曲线图形为看似枯燥的数学公式披上精彩纷呈的美丽 ...
- 【IDEA】创建Maven工程
当前工程,点new - project 选Maven,不需要点选什么骨架创建,骨架创建要下载大量依赖,生成时间太长, 空Maven的目的是让我们自己了解这个项目结构,需要什么依赖再加什么依赖 框线内的 ...
- 【Vue】Re16 Router 第三部分(懒加载、子路由)
一.配置路由懒加载 懒加载的原因: 因为组件不断的增加,项目的路由会越来越多 打包后的文件越来越大,当超过IO读写的瓶颈时,项目加载就很慢了 所以需要将路由文件分离,在被调用时进行加载 分析路由ind ...
- 【转载】 校正Ubuntu时间为北京时间
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/qq_37421762/article/de ...
- 从分布式计算的角度看pytorch和TensorFlow哪个更优?
背景: pytorch框架是一个从学术圈出来的框架,因此pytorch并不原生支持分布式计算,而且在大模型火爆的今年以外好像在深度学习领域使用分布式计算的场景确实不多,所以pytorch并不原生支持分 ...
- B. BerSU Ball
B. BerSU Ball 排序后考虑\(dp\),\(dp_{i,j}\)表示前\(i\)个男生和前\(j\)个女生能匹配的最大数. \[\begin{aligned} if(|a_i - b_j| ...