Angular 面试题库
一、Angular 基础
1.1 什么是 Angular?它的核心特性是什么?
面试回答思路:
Angular 是由 Google 开发和维护的一个完整的前端应用框架。我可以从以下几个方面介绍它的核心特性:
1. 完整的框架
- Angular 是一个完整的解决方案,不仅仅是一个库
- 内置了路由、HTTP 客户端、表单处理、动画等功能
- 提供了完整的开发工具链(CLI、测试工具等)
- 开箱即用,不需要选择和集成各种第三方库
2. TypeScript
- Angular 使用 TypeScript 编写
- 提供了强类型检查和更好的 IDE 支持
- 代码更易维护,错误更容易在编译时发现
- 支持最新的 JavaScript 特性
3. 组件化架构
- 应用由组件树构成
- 每个组件包含模板、样式和逻辑
- 组件可以嵌套和复用
- 清晰的组件层次结构
4. 依赖注入(DI)
- Angular 有强大的依赖注入系统
- 提高了代码的可测试性和可维护性
- 便于管理组件之间的依赖关系
- 支持多级注入器
5. 双向数据绑定
- 通过 [(ngModel)] 实现双向绑定
- 视图和模型自动同步
- 简化了表单处理
6. 指令系统
- 结构型指令:*ngIf、*ngFor、*ngSwitch
- 属性型指令:ngClass、ngStyle
- 可以自定义指令扩展功能
7. RxJS 集成
- 深度集成了 RxJS
- 使用 Observable 处理异步操作
- 强大的数据流管理能力
8. 模块化
- 使用 NgModule 组织代码
- 支持懒加载
- 便于代码分割和优化
补充说明: Angular 适合构建大型企业级应用,它的学习曲线相对陡峭,但提供了完整的解决方案和最佳实践。Angular 的版本更新较快,但向后兼容性做得很好。
1// Angular 组件示例2import { Component } from '@angular/core';34@Component({5 selector: 'app-counter',6 template: `7 <div>8 <p>Count: {{ count }}</p>9 <button (click)="increment()">Increment</button>10 </div>11 `,12 styles: [`13 div {14 padding: 20px;15 }16 `]17})18export class CounterComponent {19 count = 0;20 21 increment() {22 this.count++;23 }24}1.2 Angular、AngularJS 和 Angular 2+ 的区别
面试回答思路:
这是一个常见的面试题,需要区分 AngularJS(Angular 1.x)和现代 Angular(Angular 2+)。
AngularJS(Angular 1.x):
特点:
- 使用 JavaScript 编写
- 基于 MVC 架构
- 使用 $scope 和脏检查机制
- 双向数据绑定通过 ng-model
- 使用 Controller 和 $scope
问题:
- 性能问题(脏检查)
- 代码组织混乱
- 学习曲线陡峭
- 移动端性能差
Angular 2+(现代 Angular):
改进:
- 使用 TypeScript 编写
- 基于组件的架构
- 使用 Zone.js 和变更检测
- 性能大幅提升
- 更好的移动端支持
- 模块化设计
- 更好的工具链
主要区别:
| 特性 | AngularJS | Angular 2+ |
|---|---|---|
| 语言 | JavaScript | TypeScript |
| 架构 | MVC | 组件化 |
| 数据绑定 | 脏检查 | Zone.js |
| 性能 | 较差 | 优秀 |
| 移动端 | 不友好 | 友好 |
| 学习曲线 | 陡峭 | 中等 |
| CLI | 无 | 强大的 CLI |
版本说明:
- Angular 2 是完全重写,与 AngularJS 不兼容
- Angular 2+ 指的是 Angular 2 及以后的版本
- 从 Angular 2 开始,版本号直接叫 Angular,不再叫 Angular 2、3、4
- 现在最新版本是 Angular 17(2023年)
迁移建议:
- AngularJS 已经停止维护
- 新项目应该使用 Angular 2+
- 老项目可以考虑逐步迁移或重写
1.3 Angular 的架构和核心概念
面试回答思路:
Angular 的架构是理解整个框架的基础,我会从几个核心概念来介绍:
1. 模块(NgModule)
Angular 应用是模块化的,每个应用至少有一个根模块(AppModule)。
作用:
- 组织代码结构
- 管理依赖关系
- 配置编译器
- 支持懒加载
组成部分:
- declarations:声明组件、指令、管道
- imports:导入其他模块
- providers:提供服务
- bootstrap:启动组件
- exports:导出组件供其他模块使用
2. 组件(Component)
组件是 Angular 应用的基本构建块。
组成部分:
- 模板(Template):定义视图
- 样式(Styles):定义外观
- 类(Class):定义逻辑
- 元数据(Metadata):通过装饰器配置
特点:
- 每个组件都有一个选择器
- 组件可以嵌套
- 组件之间通过 @Input 和 @Output 通信
3. 服务(Service)
服务用于封装可复用的业务逻辑。
特点:
- 通过依赖注入使用
- 单例模式(默认)
- 可以注入到组件或其他服务中
- 用于数据共享、HTTP 请求等
4. 依赖注入(DI)
Angular 的核心特性之一。
优点:
- 解耦代码
- 提高可测试性
- 便于维护
- 支持多级注入器
5. 指令(Directive)
扩展 HTML 功能的特殊标记。
类型:
- 组件指令:带模板的指令
- 结构型指令:改变 DOM 结构(*ngIf、*ngFor)
- 属性型指令:改变元素外观或行为(ngClass、ngStyle)
6. 管道(Pipe)
用于转换模板中的数据。
内置管道:
- DatePipe:日期格式化
- UpperCasePipe:转大写
- LowerCasePipe:转小写
- CurrencyPipe:货币格式化
- PercentPipe:百分比格式化
7. 路由(Router)
管理应用的导航。
功能:
- 路由配置
- 路由守卫
- 懒加载
- 路由参数
- 子路由
架构图:
1应用2├── 模块(NgModule)3│ ├── 组件(Component)4│ │ ├── 模板(Template)5│ │ ├── 样式(Styles)6│ │ └── 类(Class)7│ ├── 服务(Service)8│ ├── 指令(Directive)9│ ├── 管道(Pipe)10│ └── 路由(Router)11└── 依赖注入(DI)实际应用: 理解 Angular 的架构对于构建大型应用至关重要。模块化的设计让代码组织更清晰,依赖注入让代码更易测试,这些都是 Angular 的优势。
二、组件通信
2.1 Angular 组件间通信的方式
面试回答思路:
Angular 提供了多种组件间通信的方式,根据组件关系的不同,可以选择不同的方案:
1. @Input 和 @Output(父子组件)
这是最基本也是最常用的通信方式。
@Input:父传子
- 父组件通过属性绑定传递数据给子组件
- 子组件使用 @Input 装饰器接收数据
- 支持数据变化检测
@Output:子传父
- 子组件使用 @Output 装饰器定义事件
- 通过 EventEmitter 发射事件
- 父组件监听事件并处理
特点:
- 单向数据流,数据流向清晰
- 类型安全
- 易于理解和维护
2. ViewChild 和 ContentChild(父访问子)
父组件可以直接访问子组件的实例。
ViewChild:
- 访问模板中的子组件
- 可以调用子组件的方法
- 可以访问子组件的属性
ContentChild:
- 访问通过内容投影传入的组件
- 用于组件封装
注意:
- 只能在 ngAfterViewInit 之后访问
- 破坏了组件的封装性,谨慎使用
3. 服务(Service)+ 依赖注入
通过共享服务实现组件间通信。
适用场景:
- 兄弟组件通信
- 跨层级组件通信
- 全局状态管理
实现方式:
- 创建一个服务
- 在服务中定义共享数据和方法
- 使用 RxJS Subject 或 BehaviorSubject
- 组件注入服务并订阅数据
优点:
- 解耦组件
- 支持多对多通信
- 便于状态管理
4. RxJS Subject
使用 RxJS 的 Subject 实现发布-订阅模式。
类型:
- Subject:普通主题
- BehaviorSubject:有初始值,会发送最新值
- ReplaySubject:会重放历史值
- AsyncSubject:只发送最后一个值
特点:
- 强大的数据流管理
- 支持多播
- 可以组合多个数据流
5. 路由参数
通过路由传递数据。
方式:
- 路径参数:/user/:id
- 查询参数:/user?id=123
- 状态参数:通过 state 传递对象
适用场景:
- 页面间传递数据
- 深度链接
- 书签支持
6. LocalStorage / SessionStorage
通过浏览器存储传递数据。
适用场景:
- 跨页面数据持久化
- 用户偏好设置
- 临时数据存储
注意:
- 只能存储字符串
- 有大小限制
- 注意安全性
选择建议:
- 父子组件:优先使用 @Input/@Output
- 兄弟组件:使用共享服务
- 跨层级:使用服务或状态管理库
- 页面间:使用路由参数
- 全局状态:使用 NgRx 或 Akita
在实际项目中,我会根据组件关系和数据流向选择合适的通信方式,保持代码的清晰和可维护性。
1// 1. @Input 和 @Output 示例2// 父组件3@Component({4 selector: 'app-parent',5 template: `6 <app-child 7 [message]="parentMessage" 8 (notify)="onNotify($event)">9 </app-child>10 `11})12export class ParentComponent {13 parentMessage = 'Hello from parent';14 15 onNotify(data: string) {16 console.log('Received from child:', data);17 }18}1920// 子组件21@Component({22 selector: 'app-child',23 template: `24 <p>{{ message }}</p>25 <button (click)="sendToParent()">Send</button>26 `27})28export class ChildComponent {29 @Input() message: string;30 @Output() notify = new EventEmitter<string>();31 32 sendToParent() {33 this.notify.emit('Data from child');34 }35}3637// 2. 服务通信示例38@Injectable({39 providedIn: 'root'40})41export class DataService {42 private dataSubject = new BehaviorSubject<string>('initial');43 data$ = this.dataSubject.asObservable();44 45 updateData(data: string) {46 this.dataSubject.next(data);47 }48}4950// 组件 A51export class ComponentA {52 constructor(private dataService: DataService) {}53 54 sendData() {55 this.dataService.updateData('Hello from A');56 }57}5859// 组件 B60export class ComponentB {61 data$: Observable<string>;62 63 constructor(private dataService: DataService) {64 this.data$ = this.dataService.data$;65 }66}三、生命周期
3.1 Angular 组件的生命周期钩子
面试回答思路:
Angular 组件有完整的生命周期,从创建到销毁的每个阶段都有对应的钩子函数。
生命周期顺序:
1. ngOnChanges()
- 在 ngOnInit 之前调用
- 当输入属性(@Input)变化时调用
- 接收 SimpleChanges 对象,包含变化的详细信息
- 首次调用发生在 ngOnInit 之前
使用场景:
- 响应输入属性的变化
- 对比新旧值
- 执行相应的逻辑
2. ngOnInit()
- 组件初始化时调用一次
- 在第一次 ngOnChanges 之后调用
- 此时输入属性已经设置完成
使用场景:
- 初始化组件
- 获取初始数据
- 设置订阅
3. ngDoCheck()
- 在每次变更检测时调用
- 可以实现自定义的变更检测逻辑
- 调用频率很高,注意性能
使用场景:
- 检测 Angular 无法自动检测的变化
- 实现自定义的脏检查
4. ngAfterContentInit()
- 内容投影(ng-content)初始化后调用一次
- 在第一次 ngDoCheck 之后调用
使用场景:
- 访问投影内容
- 初始化投影内容相关的逻辑
5. ngAfterContentChecked()
- 每次检查投影内容后调用
- 在 ngAfterContentInit 和每次 ngDoCheck 之后调用
使用场景:
- 响应投影内容的变化
6. ngAfterViewInit()
- 视图初始化完成后调用一次
- 此时可以访问 ViewChild 和 ViewChildren
使用场景:
- 访问子组件
- 操作 DOM
- 初始化第三方库
7. ngAfterViewChecked()
- 每次检查视图后调用
- 在 ngAfterViewInit 和每次 ngAfterContentChecked 之后调用
使用场景:
- 响应视图的变化
8. ngOnDestroy()
- 组件销毁前调用
- 用于清理工作
使用场景:
- 取消订阅
- 清除定时器
- 解绑事件监听器
- 释放资源
1import { Component, OnInit, OnDestroy, OnChanges, 2 AfterViewInit, SimpleChanges } from '@angular/core';34@Component({5 selector: 'app-lifecycle',6 template: `<div>Lifecycle Demo</div>`7})8export class LifecycleComponent implements OnInit, OnDestroy, 9 OnChanges, AfterViewInit {10 @Input() data: string;11 12 // 1. 输入属性变化13 ngOnChanges(changes: SimpleChanges) {14 console.log('ngOnChanges', changes);15 }16 17 // 2. 组件初始化18 ngOnInit() {19 console.log('ngOnInit');20 // 初始化数据、订阅等21 }22 23 // 3. 视图初始化完成24 ngAfterViewInit() {25 console.log('ngAfterViewInit');26 // 访问 ViewChild、操作 DOM27 }28 29 // 4. 组件销毁30 ngOnDestroy() {31 console.log('ngOnDestroy');32 // 清理工作:取消订阅、清除定时器33 }34}执行顺序总结:
1constructor2↓3ngOnChanges (如果有 @Input)4↓5ngOnInit6↓7ngDoCheck8↓9ngAfterContentInit10↓11ngAfterContentChecked12↓13ngAfterViewInit14↓15ngAfterViewChecked16↓17(更新时重复 ngDoCheck 到 ngAfterViewChecked)18↓19ngOnDestroy注意事项:
- 不要在 ngAfterViewInit 中修改视图:会导致 ExpressionChangedAfterItHasBeenCheckedError
- ngDoCheck 性能问题:调用频率很高,避免复杂操作
- 记得取消订阅:在 ngOnDestroy 中取消所有订阅,防止内存泄漏
四、依赖注入
4.1 什么是依赖注入?Angular 如何实现?
面试回答思路:
依赖注入(Dependency Injection,DI)是 Angular 的核心特性之一,也是其与其他框架的重要区别。
什么是依赖注入:
依赖注入是一种设计模式,用于实现控制反转(IoC)。简单来说,就是不在组件内部创建依赖对象,而是由外部注入。
为什么需要依赖注入:
1. 解耦代码
- 组件不需要知道依赖的具体实现
- 便于替换依赖
- 提高代码的灵活性
2. 提高可测试性
- 可以轻松注入 mock 对象
- 便于单元测试
- 隔离测试环境
3. 便于维护
- 依赖关系清晰
- 修改依赖不影响使用方
- 符合开闭原则
4. 单例模式
- 服务默认是单例的
- 避免重复创建实例
- 便于状态共享
Angular 的依赖注入系统:
1. 注入器(Injector)
Angular 有多级注入器:
- 根注入器(Root Injector)
- 模块注入器(Module Injector)
- 组件注入器(Component Injector)
2. 提供者(Provider)
定义如何创建依赖对象。
提供者类型:
1// 1. 类提供者(最常用)2providers: [UserService]3// 等价于4providers: [{ provide: UserService, useClass: UserService }]56// 2. 值提供者7providers: [{ provide: API_URL, useValue: 'https://api.example.com' }]89// 3. 工厂提供者10providers: [{11 provide: Logger,12 useFactory: (config: Config) => {13 return config.debug ? new DebugLogger() : new ProductionLogger();14 },15 deps: [Config]16}]1718// 4. 别名提供者19providers: [20 NewService,21 { provide: OldService, useExisting: NewService }22]3. 注入令牌(Injection Token)
用于注入非类类型的依赖。
1import { InjectionToken } from '@angular/core';23export const API_URL = new InjectionToken<string>('api.url');45// 提供值6providers: [7 { provide: API_URL, useValue: 'https://api.example.com' }8]910// 注入使用11constructor(@Inject(API_URL) private apiUrl: string) {}4. 注入范围
1// 1. 根级别(推荐)2@Injectable({3 providedIn: 'root'4})5export class UserService {}67// 2. 模块级别8@NgModule({9 providers: [UserService]10})1112// 3. 组件级别13@Component({14 providers: [UserService]15})5. 可选依赖和多个依赖
1import { Optional, SkipSelf, Self, Host } from '@angular/core';23constructor(4 // 可选依赖5 @Optional() private logger: Logger,6 7 // 跳过当前注入器8 @SkipSelf() private parentService: ParentService,9 10 // 只在当前注入器查找11 @Self() private localService: LocalService,12 13 // 在宿主组件查找14 @Host() private hostService: HostService15) {}实际应用示例:
1// 服务定义2@Injectable({3 providedIn: 'root'4})5export class DataService {6 private data: string[] = [];7 8 getData() {9 return this.data;10 }11 12 addData(item: string) {13 this.data.push(item);14 }15}1617// 组件使用18@Component({19 selector: 'app-data-list',20 template: `21 <ul>22 <li *ngFor="let item of data">{{ item }}</li>23 </ul>24 `25})26export class DataListComponent implements OnInit {27 data: string[];28 29 constructor(private dataService: DataService) {}30 31 ngOnInit() {32 this.data = this.dataService.getData();33 }34}依赖注入的优势:
- 代码更清晰,依赖关系一目了然
- 便于单元测试,可以轻松 mock 依赖
- 提高代码复用性
- 便于维护和扩展
五、RxJS 和响应式编程
5.1 什么是 RxJS?为什么 Angular 使用 RxJS?
面试回答思路:
RxJS 是 Angular 的核心依赖之一,理解 RxJS 对于掌握 Angular 至关重要。
什么是 RxJS:
RxJS(Reactive Extensions for JavaScript)是一个使用 Observable 序列来编写异步和基于事件的程序的库。
核心概念:
1. Observable(可观察对象)
- 表示一个可调用的未来值或事件的集合
- 是数据流的生产者
- 可以发出多个值
- 支持同步和异步
2. Observer(观察者)
- 消费 Observable 发出的值
- 包含三个回调函数:next、error、complete
3. Subscription(订阅)
- 表示 Observable 的执行
- 用于取消执行
4. Operators(操作符)
- 用于处理数据流的纯函数
- 可以组合使用
- 分为创建、转换、过滤、组合等类型
5. Subject
- 既是 Observable 又是 Observer
- 可以多播(multicast)
为什么 Angular 使用 RxJS:
1. 统一的异步处理
- HTTP 请求
- 用户输入事件
- 定时器
- WebSocket
2. 强大的数据流管理
- 可以轻松组合多个数据流
- 丰富的操作符
- 声明式编程
3. 更好的错误处理
- 统一的错误处理机制
- 可以重试、降级
4. 取消和清理
- 通过 unsubscribe 取消订阅
- 防止内存泄漏
基本使用示例:
1import { Observable, of, from, interval } from 'rxjs';2import { map, filter, take } from 'rxjs/operators';34// 1. 创建 Observable5const observable = new Observable(subscriber => {6 subscriber.next(1);7 subscriber.next(2);8 subscriber.next(3);9 subscriber.complete();10});1112// 2. 订阅 Observable13observable.subscribe({14 next: value => console.log(value),15 error: err => console.error(err),16 complete: () => console.log('Complete')17});1819// 3. 使用操作符20const numbers$ = of(1, 2, 3, 4, 5);2122numbers$.pipe(23 filter(n => n % 2 === 0), // 过滤偶数24 map(n => n * 2) // 乘以 225).subscribe(value => console.log(value)); // 输出: 4, 82627// 4. 定时器28const timer$ = interval(1000).pipe(29 take(5) // 只取前 5 个值30);3132timer$.subscribe(value => console.log(value)); // 0, 1, 2, 3, 4在 Angular 中的应用:
1. HTTP 请求
1import { HttpClient } from '@angular/common/http';2import { Observable } from 'rxjs';3import { map, catchError } from 'rxjs/operators';45@Injectable({6 providedIn: 'root'7})8export class UserService {9 constructor(private http: HttpClient) {}10 11 getUsers(): Observable<User[]> {12 return this.http.get<User[]>('/api/users').pipe(13 map(users => users.filter(u => u.active)),14 catchError(error => {15 console.error('Error:', error);16 return of([]);17 })18 );19 }20}2122// 组件中使用23export class UserListComponent implements OnInit {24 users$: Observable<User[]>;25 26 constructor(private userService: UserService) {}27 28 ngOnInit() {29 this.users$ = this.userService.getUsers();30 }31}3233// 模板中使用 async 管道34<div *ngFor="let user of users$ | async">35 {{ user.name }}36</div>2. 表单处理
1import { FormControl } from '@angular/forms';2import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';34export class SearchComponent implements OnInit {5 searchControl = new FormControl();6 results$: Observable<any[]>;7 8 constructor(private searchService: SearchService) {}9 10 ngOnInit() {11 this.results$ = this.searchControl.valueChanges.pipe(12 debounceTime(300), // 防抖13 distinctUntilChanged(), // 去重14 switchMap(term => 15 this.searchService.search(term)16 )17 );18 }19}3. 组件通信
1@Injectable({2 providedIn: 'root'3})4export class MessageService {5 private messageSubject = new Subject<string>();6 message$ = this.messageSubject.asObservable();7 8 sendMessage(message: string) {9 this.messageSubject.next(message);10 }11}1213// 发送消息14export class SenderComponent {15 constructor(private messageService: MessageService) {}16 17 send() {18 this.messageService.sendMessage('Hello');19 }20}2122// 接收消息23export class ReceiverComponent implements OnInit, OnDestroy {24 private subscription: Subscription;25 26 constructor(private messageService: MessageService) {}27 28 ngOnInit() {29 this.subscription = this.messageService.message$.subscribe(30 message => console.log(message)31 );32 }33 34 ngOnDestroy() {35 this.subscription.unsubscribe();36 }37}常用操作符:
创建操作符:
- of:创建发出指定值的 Observable
- from:从数组、Promise 等创建 Observable
- interval:定时发出递增的数字
- timer:延迟后发出值
转换操作符:
- map:转换每个值
- mergeMap/flatMap:映射并合并
- switchMap:映射并切换到最新的
- concatMap:映射并按顺序连接
过滤操作符:
- filter:过滤值
- take:取前 n 个值
- takeUntil:直到另一个 Observable 发出值
- debounceTime:防抖
- distinctUntilChanged:去重
组合操作符:
- combineLatest:组合最新值
- merge:合并多个 Observable
- concat:按顺序连接
- forkJoin:等待所有完成
错误处理:
- catchError:捕获错误
- retry:重试
- retryWhen:条件重试
最佳实践:
- 总是取消订阅
1export class MyComponent implements OnInit, OnDestroy {2 private subscription: Subscription;3 4 ngOnInit() {5 this.subscription = this.dataService.getData().subscribe();6 }7 8 ngOnDestroy() {9 this.subscription.unsubscribe();10 }11}1213// 或使用 takeUntil14private destroy$ = new Subject<void>();1516ngOnInit() {17 this.dataService.getData()18 .pipe(takeUntil(this.destroy$))19 .subscribe();20}2122ngOnDestroy() {23 this.destroy$.next();24 this.destroy$.complete();25}- 使用 async 管道
1<!-- 自动订阅和取消订阅 -->2<div *ngIf="data$ | async as data">3 {{ data.name }}4</div>- 避免嵌套订阅
1// ❌ 不好2this.service1.getData().subscribe(data1 => {3 this.service2.getData(data1).subscribe(data2 => {4 // ...5 });6});78// ✅ 好9this.service1.getData().pipe(10 switchMap(data1 => this.service2.getData(data1))11).subscribe(data2 => {12 // ...13});六、表单处理
6.1 Angular 的表单类型和使用
面试回答思路:
Angular 提供了两种处理表单的方式:模板驱动表单和响应式表单。
1. 模板驱动表单(Template-Driven Forms)
特点:
- 逻辑主要在模板中
- 使用 ngModel 双向绑定
- 适合简单表单
- 类似 AngularJS 的方式
基本使用:
1import { FormsModule } from '@angular/forms';23@Component({4 selector: 'app-template-form',5 template: `6 <form #form="ngForm" (ngSubmit)="onSubmit(form)">7 <input 8 name="username" 9 [(ngModel)]="user.username"10 required11 minlength="3"12 #username="ngModel"13 />14 15 <div *ngIf="username.invalid && username.touched">16 <div *ngIf="username.errors?.['required']">17 Username is required18 </div>19 <div *ngIf="username.errors?.['minlength']">20 Username must be at least 3 characters21 </div>22 </div>23 24 <input 25 name="email" 26 [(ngModel)]="user.email"27 required28 email29 />30 31 <button type="submit" [disabled]="form.invalid">32 Submit33 </button>34 </form>35 `36})37export class TemplateFormComponent {38 user = {39 username: '',40 email: ''41 };42 43 onSubmit(form: NgForm) {44 if (form.valid) {45 console.log(this.user);46 }47 }48}2. 响应式表单(Reactive Forms)
特点:
- 逻辑主要在组件类中
- 更灵活、更强大
- 适合复杂表单
- 更易于测试
- 推荐使用
基本使用:
1import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';23@Component({4 selector: 'app-reactive-form',5 template: `6 <form [formGroup]="userForm" (ngSubmit)="onSubmit()">7 <input formControlName="username" />8 9 <div *ngIf="username.invalid && username.touched">10 <div *ngIf="username.errors?.['required']">11 Username is required12 </div>13 <div *ngIf="username.errors?.['minlength']">14 Username must be at least 3 characters15 </div>16 </div>17 18 <input formControlName="email" />19 20 <div formGroupName="address">21 <input formControlName="street" />22 <input formControlName="city" />23 </div>24 25 <button type="submit" [disabled]="userForm.invalid">26 Submit27 </button>28 </form>29 `30})31export class ReactiveFormComponent implements OnInit {32 userForm: FormGroup;33 34 constructor(private fb: FormBuilder) {}35 36 ngOnInit() {37 this.userForm = this.fb.group({38 username: ['', [Validators.required, Validators.minLength(3)]],39 email: ['', [Validators.required, Validators.email]],40 address: this.fb.group({41 street: [''],42 city: ['']43 })44 });45 46 // 监听值变化47 this.userForm.valueChanges.subscribe(value => {48 console.log('Form value:', value);49 });50 51 // 监听特定字段52 this.username.valueChanges.subscribe(value => {53 console.log('Username:', value);54 });55 }56 57 get username() {58 return this.userForm.get('username');59 }60 61 onSubmit() {62 if (this.userForm.valid) {63 console.log(this.userForm.value);64 }65 }66}3. 自定义验证器
1// 同步验证器2function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {3 return (control: AbstractControl): ValidationErrors | null => {4 const forbidden = nameRe.test(control.value);5 return forbidden ? { forbiddenName: { value: control.value } } : null;6 };7}89// 异步验证器10function uniqueUsernameValidator(userService: UserService): AsyncValidatorFn {11 return (control: AbstractControl): Observable<ValidationErrors | null> => {12 return userService.checkUsername(control.value).pipe(13 map(exists => exists ? { usernameTaken: true } : null),14 catchError(() => of(null))15 );16 };17}1819// 使用20this.userForm = this.fb.group({21 username: ['', 22 [Validators.required, forbiddenNameValidator(/admin/i)],23 [uniqueUsernameValidator(this.userService)]24 ]25});4. 动态表单
1export class DynamicFormComponent {2 form: FormGroup;3 4 constructor(private fb: FormBuilder) {5 this.form = this.fb.group({6 items: this.fb.array([])7 });8 }9 10 get items() {11 return this.form.get('items') as FormArray;12 }13 14 addItem() {15 this.items.push(this.fb.group({16 name: ['', Validators.required],17 quantity: [1, [Validators.required, Validators.min(1)]]18 }));19 }20 21 removeItem(index: number) {22 this.items.removeAt(index);23 }24}选择建议:
- 简单表单:模板驱动表单
- 复杂表单:响应式表单
- 需要动态添加字段:响应式表单
- 需要复杂验证:响应式表单
七、路由和导航
7.1 Angular Router 的使用和路由守卫
面试回答思路:
Angular Router 是 Angular 的官方路由库,功能强大且完善。
基本路由配置:
1import { Routes, RouterModule } from '@angular/router';23const routes: Routes = [4 { path: '', redirectTo: '/home', pathMatch: 'full' },5 { path: 'home', component: HomeComponent },6 { path: 'about', component: AboutComponent },7 { path: 'users/:id', component: UserDetailComponent },8 { path: '**', component: NotFoundComponent }9];1011@NgModule({12 imports: [RouterModule.forRoot(routes)],13 exports: [RouterModule]14})15export class AppRoutingModule {}路由导航:
1// 1. 模板中导航2<a routerLink="/home">Home</a>3<a [routerLink]="['/users', userId]">User</a>45// 2. 编程式导航6export class MyComponent {7 constructor(private router: Router) {}8 9 goToHome() {10 this.router.navigate(['/home']);11 }12 13 goToUser(id: number) {14 this.router.navigate(['/users', id]);15 }16 17 goToUserWithQuery() {18 this.router.navigate(['/users'], {19 queryParams: { page: 1, size: 10 }20 });21 }22}获取路由参数:
1import { ActivatedRoute } from '@angular/router';23export class UserDetailComponent implements OnInit {4 userId: string;5 6 constructor(private route: ActivatedRoute) {}7 8 ngOnInit() {9 // 1. 快照方式(适用于参数不会变化的情况)10 this.userId = this.route.snapshot.paramMap.get('id');11 12 // 2. Observable 方式(适用于参数可能变化的情况)13 this.route.paramMap.subscribe(params => {14 this.userId = params.get('id');15 });16 17 // 3. 查询参数18 this.route.queryParamMap.subscribe(params => {19 const page = params.get('page');20 const size = params.get('size');21 });22 }23}路由守卫(Route Guards):
1. CanActivate(进入守卫)
1import { Injectable } from '@angular/core';2import { CanActivate, Router } from '@angular/router';34@Injectable({5 providedIn: 'root'6})7export class AuthGuard implements CanActivate {8 constructor(9 private authService: AuthService,10 private router: Router11 ) {}12 13 canActivate(): boolean {14 if (this.authService.isLoggedIn()) {15 return true;16 }17 18 this.router.navigate(['/login']);19 return false;20 }21}2223// 使用24const routes: Routes = [25 {26 path: 'admin',27 component: AdminComponent,28 canActivate: [AuthGuard]29 }30];2. CanDeactivate(离开守卫)
1export interface CanComponentDeactivate {2 canDeactivate: () => boolean | Observable<boolean>;3}45@Injectable({6 providedIn: 'root'7})8export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {9 canDeactivate(component: CanComponentDeactivate): boolean | Observable<boolean> {10 return component.canDeactivate ? component.canDeactivate() : true;11 }12}1314// 组件实现15export class EditComponent implements CanComponentDeactivate {16 hasUnsavedChanges = false;17 18 canDeactivate(): boolean {19 if (this.hasUnsavedChanges) {20 return confirm('You have unsaved changes. Do you really want to leave?');21 }22 return true;23 }24}3. Resolve(预加载数据)
1@Injectable({2 providedIn: 'root'3})4export class UserResolver implements Resolve<User> {5 constructor(private userService: UserService) {}6 7 resolve(route: ActivatedRouteSnapshot): Observable<User> {8 const id = route.paramMap.get('id');9 return this.userService.getUser(id);10 }11}1213// 路由配置14{15 path: 'users/:id',16 component: UserDetailComponent,17 resolve: { user: UserResolver }18}1920// 组件中使用21ngOnInit() {22 this.route.data.subscribe(data => {23 this.user = data['user'];24 });25}懒加载:
1const routes: Routes = [2 {3 path: 'admin',4 loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)5 }6];八、性能优化
8.1 Angular 性能优化的方法
面试回答思路:
Angular 性能优化涉及多个方面,我会从几个关键点来介绍:
1. 变更检测优化
使用 OnPush 策略:
1@Component({2 selector: 'app-user',3 changeDetection: ChangeDetectionStrategy.OnPush,4 template: `<div>{{ user.name }}</div>`5})6export class UserComponent {7 @Input() user: User;8}手动触发变更检测:
1constructor(private cdr: ChangeDetectorRef) {}23updateData() {4 // 更新数据5 this.data = newData;6 // 手动触发检测7 this.cdr.markForCheck();8}2. TrackBy 函数
1@Component({2 template: `3 <div *ngFor="let item of items; trackBy: trackByFn">4 {{ item.name }}5 </div>6 `7})8export class ListComponent {9 items: Item[];10 11 trackByFn(index: number, item: Item) {12 return item.id; // 使用唯一标识13 }14}3. 懒加载模块
1const routes: Routes = [2 {3 path: 'feature',4 loadChildren: () => import('./feature/feature.module')5 .then(m => m.FeatureModule)6 }7];4. 使用 Pure Pipes
1@Pipe({2 name: 'filter',3 pure: true // 默认就是 true4})5export class FilterPipe implements PipeTransform {6 transform(items: any[], searchText: string): any[] {7 if (!items || !searchText) {8 return items;9 }10 return items.filter(item => 11 item.name.toLowerCase().includes(searchText.toLowerCase())12 );13 }14}5. 虚拟滚动
1import { ScrollingModule } from '@angular/cdk/scrolling';23@Component({4 template: `5 <cdk-virtual-scroll-viewport itemSize="50" class="viewport">6 <div *cdkVirtualFor="let item of items">7 {{ item.name }}8 </div>9 </cdk-virtual-scroll-viewport>10 `11})12export class VirtualScrollComponent {13 items = Array.from({length: 10000}).map((_, i) => ({14 id: i,15 name: `Item ${i}`16 }));17}6. AOT 编译
1# 生产构建默认使用 AOT2ng build --prod7. 预加载策略
1import { PreloadAllModules } from '@angular/router';23@NgModule({4 imports: [5 RouterModule.forRoot(routes, {6 preloadingStrategy: PreloadAllModules7 })8 ]9})10export class AppRoutingModule {}8. 取消订阅
1export class MyComponent implements OnDestroy {2 private destroy$ = new Subject<void>();3 4 ngOnInit() {5 this.dataService.getData()6 .pipe(takeUntil(this.destroy$))7 .subscribe();8 }9 10 ngOnDestroy() {11 this.destroy$.next();12 this.destroy$.complete();13 }14}九、总结
这份 Angular 面试题库涵盖了:
- ✅ Angular 基础概念和核心特性
- ✅ 架构和核心概念
- ✅ 组件通信
- ✅ 生命周期
- ✅ 依赖注入
- ✅ RxJS 和响应式编程
- ✅ 表单处理
- ✅ 路由和导航
- ✅ 性能优化
建议结合实际项目经验,深入理解每个知识点的应用场景和最佳实践。
评论区 / Comments