ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Angular深度解析:核心原理、面试考点与项目实践

Angular深度解析:核心原理、面试考点与项目实践 1. 先说说那个被反复唱衰又一直活得很好的 Angular这两年面试前端我特别喜欢问一个问题你觉得 Angular 怎么样十个里有六七个会说太重了生态不如 React国内用的人少但真让他说出重在哪、哪里不好用又支支吾吾。这种印象流的评价恰恰说明 Angular 在国内被误解得有多深。先给这篇文章定个调。我做了七八年前端React、Vue、Angular 都上过生产项目。Angular 是我个人认为下限最高的框架——也就是说一个水平一般的新手用 Angular 写出来的代码质量大概率比用 React 或 Vue 写出来的要规整。这不是玄学是框架的强约束带来的。代价就是你需要先理解它的思维方式而不是像 Vue 那样拿起来就能跑。这篇文章不打算写成官方文档的翻译而是从我这几年实际做项目、带新人、面试候选人的角度把 Angular 的核心知识点、高频面试题、真实项目里踩过的坑串一遍。适合三类人看一是准备用 Angular 做正式项目的团队二是正在准备 Angular 相关岗位面试的人三是写了两三年 Vue 想横向对比理解的同行。不管你是哪一类看完至少能对 Angular 的重有一个客观的认识它重在哪哪些重量是必须的哪些是历史包袱以及这些重量换来的是什么。2. 核心知识点扫盲Angular 的重到底重在哪里Angular 的完整知识体系摊开来看确实吓人模块、组件、模板、指令、管道、服务、依赖注入、RxJS、路由、表单、HTTP 客户端、变更检测……但如果你真正理解了它的设计逻辑这些东西其实是围绕一条主线展开的不是散落的碎片。2.1 组件和模板一切从 Component 开始Angular 的组件和 Vue、React 的组件在思路上是同源的——UI 拆分成可复用的部件。但 Angular 的组件有一个显著特点它是一个装饰器类而不是一个函数。Component({ selector: app-user-card, templateUrl: ./user-card.component.html, styleUrls: [./user-card.component.scss], changeDetection: ChangeDetectionStrategy.OnPush }) export class UserCardComponent implements OnInit { Input() user: User; Output() cardClick new EventEmitterUser(); constructor(private userService: UserService) {} ngOnInit(): void { // 初始化逻辑 } }这个看起来简单的结构背后有几个面试官特别爱抠的点selector 命名必须带前缀app-这是为了避免和原生 HTML 标签冲突也是 Angular 官方强力推荐的风格。很多新手写成selector: user-card浏览器不报错但等到做 Web Component 或者和其他框架混用的时候就会踩坑。模板和样式是组件私有的。styleUrls里的样式默认做了 ViewEncapsulation.Emulated 处理也就是 Angular 通过给元素加属性选择器的方式模拟 Shadow DOM让组件样式不会泄漏到全局。这也是新手经常疑惑为什么我写的全局样式不生效的原因之一。组件生命周期不止是 ngOnInit。很多人以为写了 ngOnInit 就完事了实际上 Angular 的组件生命周期有八个钩子ngOnChanges、ngOnInit、ngDoCheck、ngAfterContentInit、ngAfterContentChecked、ngAfterViewInit、ngAfterViewChecked、ngOnDestroy。其中ngOnChanges只在Input属性发生变化时触发而且只有父组件传入的绑定值变化才会触发组件内部自己修改Input属性不会触发这个区别面试常常考。2.2 NgModule被骂得最惨但也最有价值的设计NgModule 大概是 Angular 被吐槽最多的部分。Vue 和 React 的组件想用直接用Angular 却要先在 modules 里声明。很多从 Vue 转过来的人第一个月都在骂这个设计。但你冷静下来想想NgModule 解决的是一个真实的工程问题当项目大到几百个组件、几十个库的时候你怎么知道哪些组件能用、哪些服务可用Vue 和 React 靠的是约定优于配置——默认都能用出了问题再查。Angular 的 NgModule 则是显式声明这个模块里有什么这个模块依赖什么清清楚楚。NgModule({ declarations: [UserCardComponent, UserListComponent], imports: [CommonModule, FormsModule, SharedModule], providers: [UserService], exports: [UserCardComponent] }) export class UserModule {}这里面有几个实用经验declarations 里声明的组件只能在本模块内部使用别的模块要用必须先在 exports 里导出去。这是新手最容易撞的墙我明明在 Module A 里声明了怎么 Module B 用不了——答案是你要在 Module A 的 exports 里导出它。不要在多个模块的 providers 里重复提供同一个服务除非你明确知道自己在做什么。这会导致服务实例不唯一状态不同步而且很难排查。延迟加载的模块里providers 中提供的服务是模块级单例只有在这个模块被加载后才存在。如果核心服务放在了懒加载模块里而根模块的组件又依赖它就会拿到两个不同实例。这个坑我见过不下三次。2.3 模板语法和指令Angular 的模板即代码Angular 的模板语法比 Vue 的更像普通 HTML但它有几个一旦用错就非常隐蔽的点。结构性指令的星号语法div *ngIfisLoggedIn; else guestBlock欢迎回来/div ng-template #guestBlock请先登录/ng-template div *ngForlet item of items; trackBy: trackByFn; index as i {{ i }} - {{ item.name }} /div这里的*ngIf和*ngFor本质是语法糖展开后是ng-template包裹的结构。理解这一点非常重要因为你一旦需要做复杂条件判断比如多个 else 分支就要直接使用ng-template的完整写法。另一个容易忽略的是trackBy。*ngFor默认用对象引用做 diff如果你从后端拉了一个新的数组哪怕数据一模一样也会全部重新渲染。加上trackBy用唯一 ID 追踪性能差异在列表数据量几百上千的时候特别明显。这既是实践技巧也是面试常考的点。属性绑定 vs 插值!-- 错误这样绑的是字符串 -- img src{{ imageUrl }} !-- 正确方括号是真正的属性绑定 -- img [src]imageUrl !-- 事件绑定 -- button (click)handleClick($event)点击/button !-- 双向绑定是属性绑定和事件绑定的组合语法 -- input [(ngModel)]user.name很多人写 Angular 模板容易把[]、()、[()]的适用场景搞混。记住一个核心规则方括号是把数据从组件流向模板括号是把事件从模板流向组件方括号加圆括号是双向流。理解了这条规则模板语法基本就通了。2.4 服务和依赖注入Angular 的组装工厂依赖注入DI是 Angular 区别 Vue 的最大亮点。Vue 和 React 的组件间通信最终都绕不开 props 层层传递或者引入全局状态管理。Angular 的 DI 系统让你可以在任意层级注入服务而且是构造器注入的方式Injectable({ providedIn: root }) export class AuthService { private currentUser: User | null null; login(username: string, password: string): ObservableAuthResult { return this.http.postAuthResult(/api/login, { username, password }) .pipe(tap(res this.currentUser res.user)); } getCurrentUser(): User | null { return this.currentUser; } }providedIn: root的意思是把这个服务注册为全局单例整个应用共享一个实例。这是 RxJS 的 Observable 状态管理的基础——多个组件注入同一个服务它们订阅的都是同一份数据流。DI 系统的层级关系也是面试重点组件级别的 providers 让每个组件实例拥有独立的服务实例模块级别的 providers 让模块下的所有组件共享实例根级别就是全应用共享。我见过有人因为理解不了这个层级在子组件里重新提供了全局服务导致状态死活不同步排查了两三天。2.5 RxJSAngular 的灵魂也是劝退新手的最大门槛Angular 的 HTTP、表单、路由事件、甚至点击事件都可以是 Observable。很多新手从 Promise 的思维转到 Observable 的时候特别难受为什么我拿不到数据为什么订阅了两次发起了两个请求为什么页面销毁了还在请求核心区别就一句话Promise 是立即执行、只发一次Observable 是惰性执行、可以多次发射。而且 Observable 是冷启动的只有调用subscribe()才会真正发起执行。这是 Angular 开发者最容易犯的错误在模板里直接调用返回 Observable 的方法导致每次变更检测都发起新的 HTTP 请求。// 错误每次变更检测都会重新请求 // div{{ getUserName() }}/div getUserName(): Observablestring { return this.http.getstring(/api/user/name); } // 正确在 ngOnInit 里订阅一次存到普通属性 userName: string ; ngOnInit() { this.http.getstring(/api/user/name).subscribe(name { this.userName name; }); }在 Angular 项目里什么时候 unsubscribe 是一件必须养成肌肉记忆的事。最稳妥的方案是使用AsyncPipe模板里自动管理订阅生命周期或者用takeUntilDestroyed操作符。后者是 Angular 16 以后非常推荐的方式this.http.getstring(/api/user/name) .pipe(takeUntilDestroyed()) .subscribe(name this.userName name);3. Angular 和 Vue 到底差在哪别再拿国内用的人少说事了既然热搜词里有Angular 和 Vue 的区别这基本上是每个转 Angular 的人都会纠结的问题。我用自己两个框架都上过生产的经历从几个关键维度拆一下。3.1 架构理念全家桶 vs 渐进式Vue 的核心理念是渐进式——你可以在一个旧项目里引入 Vue 只做一个弹窗组件也可以搭一个完整的 Vue 全家桶项目。这种灵活性让 Vue 的上手成本极低团队可以零成本试水。Angular 是另一个极端开箱即用的全家桶。路由、HTTP、表单、依赖注入、测试工具、国际化官方全部给你配齐了。你不需要像 React 那样纠结选 Redux 还是 MobXAngular 生态里 NgRx 基本是最主流的选择。这就意味着团队的决策成本大大降低但代价是框架的学习曲线更陡峭——你不能先会一点再用至少要把组件、模块、服务、DI 这套基础先搞清楚。3.2 数据绑定和变更检测机制Vue 3 用 Proxy 实现了响应式系统数据变化自动触发视图更新开发者不需要关心什么变了。Angular 用的是脏检查 Zone.js的机制——Zone.js 猴子补丁了浏览器的所有异步 API在异步操作完成后通知 Angular 从头跑一遍变更检测。这意味着一个非常反直觉的事实Angular 的变更检测默认是全量扫描的。组件树里任何一个 Input 变化默认情况下整棵组件树都要检查一遍。性能优化的关键就比较清楚了使用ChangeDetectionStrategy.OnPush告诉 Angular 只检查 Input 引用发生了变化的组件减少无意义的检查开销。说句公道话Angular 的变更检测机制在信号Signals逐步落地之后正在改变。Angular 16 引入的信号模型本质上就是把 Vue 的细粒度响应式借过来了未来的 Angular 项目会更倾向于信号驱动的数据流。3.3 类型系统和语言选择Vue 3 虽然用 TypeScript 重写了但模板层面仍然有很多黑魔法比如 v-model 的修饰符、provide/inject 的类型推导偶尔会有心无力。Angular 从一开始就是 TypeScript 的一等公民从组件类到模板类型推导贯穿始终。模板里的类型安全是 Angular 一个很大的隐藏优势!-- Angular 能从 ts 类型推断出 user.name 是 string -- p{{ user.name.toUpperCase() }}/p这种模板类型检查strictTemplates在项目大到一定程度时真的能救你命。Vue 的模板虽然也有类似能力但实现深度和 Angular 的编译期检查还是有差距。3.4 性能体积大不代表运行慢Angular 最被诟病的就是打包体积。一个空的 Angular 15 项目 gzip 后大约 45KB 左右React 加大 Vue 大约 30KB。但说句实话在 HTTP/2 的时代几 KB 的首屏体积差异对用户感知的影响微乎其微。真正影响性能的是运行时开销。Angular 默认的变更检测机制把应用交给 Zone.js 管理在某些高频更新的场景下会有性能损耗。但是用了 OnPush 和 Signals 之后Angular 的运行时性能并不比 Vue 差。而且 Angular 的 AOT 编译能把模板直接编译成 JavaScript 指令省去了运行时解析模板的开销对于复杂页面反而占优。3.5 到底怎么选别只看热度要看场景我个人的建议是如果你的团队以中小型项目为主追求快速迭代Vue 是更省心的选择。渐进式引入、灵活度高、招人容易。如果你做的是大型企业级应用、有严格的类型安全和代码规范要求、团队规模中等偏上Angular 能让你的项目在长期维护阶段少流很多血。它的强约束到了项目后期就是优势每个人写的代码风格都接近每个模块的边界都明确接盘的人不会想死。如果你们是外包团队或者要交付给客户维护Angular 的官方 CLI、依赖注入、模块化结构让接手方更容易理解项目结构——因为大家都会按 Angular 的手册写不会写出花来。4. 面试高频考点复盘这些题答不好基本就凉了Angular 相关岗位的面试问来问去就那么几个考点。我翻了近两年被问得最多的题帮你逐条拆解一下背后的原理。光背答案没用理解原理才能应对追问。4.1 变更检测机制和 OnPush 策略面试官的经典问法是Angular 的变更检测是怎么工作的为什么用 OnPush 能提升性能标准回答思路Angular 在应用启动时创建一棵 View 树每个组件对应一个 View。当 Zone.js 检测到异步事件完成时会从根组件开始从上到下遍历所有组件检查每个组件的绑定值是否变化有变化就更新视图。OnPush 策略会让组件在没有 Input 变化、没有事件触发、没有 AsyncPipe 派发值、没有手动detectChanges()的情况下跳过检查。追问一OnPush 组件里用了普通的setTimeout改数据视图会更新吗如果组件是 OnPush单纯 setTimeout 改变了组件内部属性视图不会自动更新。需要在 setTimeout 回调里调用ChangeDetectorRef.detectChanges()或者markForCheck()。markForCheck 会标记该组件及其祖先组件会在下一轮变更检测中被检查。追问二AsyncPipe 和 OnPush 一起用有什么好处AsyncPipe 在内部调用了markForCheck()所以 OnPush 组件里用 AsyncPipe 订阅 Observable一旦 Observable 发出新值组件会自动更新视图。这也是官方推荐 OnPush AsyncPipe 组合的原因——性能和正确性兼得。4.2 Zone.js 的原理和它带来的隐患Zone.js 是整个 Angular 变更检测的触发器。它扩展了浏览器所有的异步 APIsetTimeout、Promise、addEventListener、XMLHttpRequest 等在这些钩子执行完成时通知 Angular该检查视图了。面试官如果问Zone.js 有什么问题你要知道它不是银弹所有异步操作都会触发全量变更检测包括你根本没改任何数据的定时器。性能浪费就发生在这里。Zone.js 的 patch 机制在原生 JavaScript 环境、Web Worker、部分老旧浏览器上有兼容性问题。Angular 16 之后官方在积极推进Zone-less方案也就是完全靠 Signals 驱动变更检测抛弃 Zone.js。这既是对性能的优化也是对架构的简化。面试回答时能提到这一层说明你确实在跟进 Angular 的演进。4.3 Observable 和 Promise 的区别必考题这个题本身不难但面试官会往深了追问。基础答案是Observable 是惰性的支持取消订阅支持多个值发射提供一整套操作符做流式处理Promise 是立即执行的只发射一个值不能取消。追问一你在 Angular 项目里怎么管理多个并发请求用 RxJS 的forkJoin类似 Promise.all或者combineLatestforkJoin({ users: this.http.getUser[](/api/users), posts: this.http.getPost[](/api/posts) }).subscribe(({ users, posts }) { this.users users; this.posts posts; });追问二请求失败了想重试 3 次怎么写this.http.get(/api/data) .pipe( retry(3), catchError(err { console.error(请求失败, err); return of(null); }) );追问三多个请求要按顺序执行后一个依赖前一个结果怎么办用switchMapthis.http.getUser(/api/user) .pipe( switchMap(user this.http.getPost[](/api/user/${user.id}/posts)) ) .subscribe(posts this.posts posts);4.4 路由守卫的完整执行顺序Angular 路由守卫有四种CanActivate、CanActivateChild、CanDeactivate、CanLoad。面试常考的是执行顺序和用途。一个导航从/dashboard到/admin/users守卫的完整执行顺序是先执行当前路由/admin的 CanActivateChild判断子路由能不能访问。再执行目标路由链上每个路由的 CanActivate。如果要离开当前页面还要执行当前路由的 CanDeactivate常用来提示用户有未保存的修改。如果是首次加载懒加载模块会先执行 CanLoad判断该模块是否允许被加载。这里有个非常容易混淆的点CanActivate 在模块加载后执行CanLoad 在模块加载前执行。如果你想做登录拦截用 CanActivate 就够了但如果你想阻止未登录用户下载整个懒加载模块的代码就需要 CanLoad。4.5 Angular 的生命周期钩子怎么记面试官问生命周期不是让你背名字而是考你每个钩子的调用时机和适用场景。记忆思路是分三段创建阶段ngOnChanges输入属性变化→ ngOnInit组件初始化完成→ ngDoCheck每次变更检测时调用。内容投影阶段ngAfterContentInit内容投影完成后→ ngAfterContentChecked。视图阶段ngAfterViewInit视图初始化完成后→ ngAfterViewChecked。高频考点是在 ngOnInit 里拿不到子组件的 ViewChild要等到 ngAfterViewInit 才能拿到反过来父组件传给子组件的 Input 值子组件要等 ngOnInit 或者 ngOnChanges 才能读到。这两个执行时机搞错就会出现明明传了值子组件里却是 undefined的诡异 bug。4.6 单元测试和依赖注入配合考面试官可能会问写单元测试的时候怎么 mock 掉一个 HttpClient 请求标准回答用HttpClientTestingModule。TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [UserService] }); const http TestBed.inject(HttpClientTestingController); const service TestBed.inject(UserService); service.getUser(1).subscribe(user { expect(user.name).toBe(张三); }); const req http.expectOne(/api/user/1); req.flush({ id: 1, name: 张三 });这里核心是理解TestBed.inject替代了传统的new方式依赖注入的测试优雅之处在于你不用知道服务的具体依赖链只需要替换根层的 Provider。这也是 DI 模式相比手动实例化最大的优势——可测试性。5. 真实项目里的坑与优化我踩过之后才明白的事知识点归知识点真正让 Angular 项目痛苦的不是框架本身不懂而是细节里的魔鬼。这里把我带过的几个中大型 Angular 项目中最容易踩的坑和验证有效的优化方案分享给你。5.1 内存泄漏最常见也最隐蔽的 bugAngular 项目的内存泄漏几乎都和订阅未取消有关。特别是在组件里直接订阅了全局服务比如路由事件、WebSocket 消息、自定义 Subject组件销毁后订阅仍然活着回调还在执行甚至报错。我见过最典型的场景是一个全局的 WebSocket 消息服务// 错误示范组件销毁后这个订阅还活着 this.socketService.onMessage().subscribe(msg { this.messages.push(msg); // 组件都销毁了this 还在被调用 });修复方式我在前面已经提到用AsyncPipe自动管理或者在ngOnDestroy里手动取消。但还有一个团队规范上的问题很多人以为用了takeUntilDestroyed就万事大吉却忽略了如果这个 Observable 是在构造函数里创建的自定义 Subject才更需要手动 complete 来释放资源。private destroy$ new Subjectvoid(); ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); }一句话总结所有长期存活的 Observable 订阅都必须绑定组件的生命周期。这应该写进团队的 code review 检查单里。5.2 HTTP 拦截器的正确打开方式Angular 的 HTTP 拦截器是一个非常强大的能力能在请求发出前统一加 token、统一处理错误码、统一加载动画。但它的坑在于拦截器里的依赖注入。Injectable() export class AuthInterceptor implements HttpInterceptor { intercept(req: HttpRequestany, next: HttpHandler): ObservableHttpEventany { const authService this.injector.get(AuthService); const token authService.getToken(); if (token) { req req.clone({ setHeaders: { Authorization: Bearer ${token} } }); } return next.handle(req); } }这里有个非常值得注意的点不要直接在构造函数里注入 AuthService要用Injector延迟获取。因为 HTTP 拦截器是在应用启动时注册的此时 AuthService 可能还没初始化完成直接注入可能导致循环依赖或者拿到未初始化实例。这是我踩过最冤枉的坑排查了半天才发现是初始化顺序的问题。5.3 懒加载不是拆了模块就完事Angular 的路由懒加载很简单const routes: Routes [ { path: admin, loadChildren: () import(./admin/admin.module).then(m m.AdminModule) } ];但实际项目里懒加载拆得不合理反而会让首屏更慢。几个经验不要把所有公共组件都放在 SharedModule。如果 SharedModule 太大懒加载的模块引用它时SharedModule 的代码会被打进每个 chunk 里导致重复下载。正确的做法是只把确实需要共享的组件放进去对于只在某个模块里用到的组件就地声明。预加载策略可以平衡懒加载和用户体验。PreloadAllModules会在首屏加载完成后静默加载其他模块。对于用户大概率会访问的页面这种策略能让跳转瞬开。路由守卫和懒加载结合时注意CanLoad 在模块加载前执行如果守卫失败模块不会被加载这也是减少不必要流量的一种手段。5.4 表单模板驱动和响应式表单的选择Angular 提供了两套表单方式新人经常纠结用哪个。我的建议很明确项目规模超过 5 个表单全部用响应式表单。// 响应式表单 this.form this.fb.group({ username: [, [Validators.required, Validators.minLength(3)]], email: [, [Validators.required, Validators.email]], password: [, [Validators.required, Validators.pattern(/^(?.*[a-z])(?.*[A-Z])(?.*\d).{8,}$/)]] }); // 校验错误信息 get emailErrors() { const control this.form.get(email); return control?.touched control?.invalid ? 请输入合法的邮箱地址 : ; }响应式表单的优势在于表单结构在 TypeScript 里是可预测的可以用form.valueChanges做流式监听校验逻辑可以抽成纯函数复用。模板驱动表单在小表单、快速原型的时候很爽但一旦有复杂的动态增删字段、跨字段校验就会写出一堆模板里难以阅读的代码。5.5 性能优化的三板斧第一板斧OnPush AsyncPipe。这个组合我在前面讲原理的时候反复提落实到项目里就是写组件的默认规范。除了少数确实需要默认变更检测的场景全部 OnPush。第二板斧trackBy 必须加。*ngFor里不加 trackBy列表只要一变整个 DOM 全部重建。数据量小无所谓数据量一旦上千卡顿是必然的。第三板斧懒加载 预加载策略。首屏只加载核心代码用户可能访问的模块悄悄提前加载兼顾速度和体验。还有一个容易被忽略的是图片资源Angular CLI 不会自动帮你压缩图片和生成 WebP。我在项目中统一使用ngImageOptimization或者构建时用 image-webpack-loader体积能降一半以上。5.6 状态管理什么时候需要 NgRx这是个非常实际的问题。很多团队一上来就上 NgRx结果代码量翻倍业务没复杂到那个程度维护反而更累。我的判断标准很简单如果你发现多个组件要共享同一份数据而且这份数据的更新源不止一个HTTP、WebSocket、用户操作那就上状态管理。如果只是父子组件传值、两三个页面共享一个服务数据用普通 Service BehaviorSubject 完全够了。Injectable({ providedIn: root }) export class CartService { private itemsSubject new BehaviorSubjectCartItem[]([]); items$ this.itemsSubject.asObservable(); addItem(item: CartItem) { const items [...this.itemsSubject.value, item]; this.itemsSubject.next(items); } removeItem(id: string) { const items this.itemsSubject.value.filter(i i.id ! id); this.itemsSubject.next(items); } }这个模式叫轻量级服务状态管理在中小型项目里比 NgRx 好用得多代码量少、类型安全、容易调试。等真的到了需要 undo/redo、跨模块状态追踪、复杂异步副作用的时候再迁移到 NgRx 也来得及。记住架构是演进出来的不是一步到位设计出来的。6. 从零到一学 Angular我的建议和踩坑心得最后分享一些个人学习的建议算是给新人的一条走通之路。第一不要从看文档开始要从跑项目开始。Angular 官方文档其实写得很好但如果你没有项目经验直接硬啃很容易被概念淹没。更有效的方式是先用 Angular CLI 创建一个新项目把默认的 app 跑起来然后尝试改几个地方——换一个组件、加一个路由、写一个简单的 HTTP 请求。让项目先跑起来有了体感再看文档你会发现那些概念突然都串起来了。第二优先搞懂数据怎么流动而不是去背 API。Angular 的学习难点不在于 API 多而在于数据流的理解。Input 往下传、Output 往上抛、服务共享、Observable 订阅、Store 统一管理——你把数据从哪来、到哪去、怎么变这条链路想明白了剩下的大多数 API 都是查文档就能解决的事。第三好好理解依赖注入这是 Angular 区别于所有其他框架的核心能力。我见过太多人把依赖注入单纯理解为在构造函数里声明参数。其实 DI 的价值在于控制反转模块不自己创建依赖而是声明需要什么由容器来提供。这让你的代码可测试性、可扩展性、模块独立性都会显著提升。面试的时候能把 DI 讲得深入浅出的人往往对 Angular 的理解都不会差。第四一定要动手写一个完整的小项目。学 Angular 最容易掉进的陷阱是看会了但不会写。我的建议是做一个简单的任务管理应用包含用户登录HTTP 请求 拦截器、任务列表CRUD 路由、任务详情懒加载模块、筛选功能RxJS 操作符。这个项目覆盖了 Angular 的核心知识点做完你对整个框架的信心会完全不一样。至于 Angular 的生态和未来我个人的判断是Angular 短时间内不会消失它在企业级应用赛道上的地位依然稳固。Signals 的引入说明官方也在吸收 Vue 和 React 的响应式思想框架正在变得更轻、更聪明。如果你现在开始学学到的东西在未来的两三年内都不会过时。写到这里我最后想说的是选框架这件事从来没有绝对的对错。Vue 有 Vue 的洒脱React 有 React 的自由Angular 有 Angular 的规矩。真正重要的是你想成为什么样的工程师以及你的团队想要什么样的项目。从事前端这些年我的体会是——能驾驭重型框架的人回头写什么都游刃有余而只会写轻量框架的人遇到复杂场景往往要交不少学费。Angular 的每一次学习痛苦最终都会变成你对前端架构理解的深度。
返回列表