Angular面试题目和答案大全
基础概念篇
1. 什么是Angular?它与AngularJS有什么区别?
答案: Angular是由Google开发的基于TypeScript的开源Web应用框架,用于构建单页应用程序(SPA)。
Angular vs AngularJS对比:
特性 | AngularJS | Angular |
---|---|---|
语言 | JavaScript | TypeScript |
架构 | MVC | 组件化架构 |
移动支持 | 无 | 原生支持 |
SEO | 差 | 支持服务端渲染 |
性能 | 较低 | 高性能 |
学习曲线 | 中等 | 较陡峭 |
发布时间 | 2010 | 2016 |
2. Angular的核心概念有哪些?
答案:
核心构建块:
- 模块(Modules):应用的组织单元,使用@NgModule装饰器
- 组件(Components):UI的基本构建块,控制视图
- 服务(Services):可复用的业务逻辑,通过依赖注入使用
- 指令(Directives):扩展HTML元素的行为
- 管道(Pipes):数据转换和格式化
- 路由(Router):页面导航管理
核心特性:
- 依赖注入(DI)
- 双向数据绑定
- 变更检测
- Zone.js
3. Angular的架构模式是什么?
答案: Angular采用组件化架构模式,主要特点:
分层架构:
┌─────────────────┐
│ Presentation │ ← 组件层(Components)
├─────────────────┤
│ Business │ ← 服务层(Services)
├─────────────────┤
│ Data │ ← 数据层(HTTP Client, State Management)
└─────────────────┘
设计模式:
- MVP(Model-View-Presenter)
- 依赖注入模式
- 观察者模式
- 单例模式
4. 什么是TypeScript?为什么Angular使用TypeScript?
答案: TypeScript是Microsoft开发的JavaScript超集,添加了静态类型定义。
Angular使用TypeScript的原因:
- 静态类型检查:编译时发现错误
- 更好的IDE支持:自动完成、重构等
- 面向对象编程:类、接口、泛型等
- ES6+特性支持:装饰器、模块等
- 更好的代码可维护性
// TypeScript示例
interface User {id: number;name: string;email: string;
}class UserService {getUser(id: number): User {// 实现逻辑}
}
组件篇
5. Angular组件的生命周期钩子有哪些?
答案:
生命周期顺序:
- ngOnChanges:输入属性变化时调用
- ngOnInit:初始化时调用(一次)
- ngDoCheck:每次变更检测时调用
- ngAfterContentInit:内容投影初始化后调用(一次)
- ngAfterContentChecked:每次检查投影内容后调用
- ngAfterViewInit:视图初始化后调用(一次)
- ngAfterViewChecked:每次检查视图后调用
- ngOnDestroy:销毁前调用
export class MyComponent implements OnInit, OnDestroy {ngOnInit(): void {console.log('Component initialized');}ngOnDestroy(): void {console.log('Component destroyed');}
}
6. 组件通信的方式有哪些?
答案:
1. 父子组件通信:
// 父传子:@Input
@Component({template: `<child-component [data]="parentData"></child-component>`
})
export class ParentComponent {parentData = 'Hello Child';
}@Component({selector: 'child-component'
})
export class ChildComponent {@Input() data: string;
}// 子传父:@Output
@Component({selector: 'child-component',template: `<button (click)="sendData()">Send</button>`
})
export class ChildComponent {@Output() dataEvent = new EventEmitter<string>();sendData() {this.dataEvent.emit('Hello Parent');}
}
2. 服务通信:
@Injectable()
export class DataService {private dataSubject = new BehaviorSubject<string>('');data$ = this.dataSubject.asObservable();updateData(data: string) {this.dataSubject.next(data);}
}
3. ViewChild/ViewChildren:
@Component({template: `<child-component #child></child-component>`
})
export class ParentComponent {@ViewChild('child') childComponent: ChildComponent;callChildMethod() {this.childComponent.someMethod();}
}
7. 什么是内容投影(Content Projection)?
答案: 内容投影是Angular中将外部内容插入到组件模板指定位置的机制,类似于Vue的插槽。
单插槽投影:
// 子组件
@Component({selector: 'card',template: `<div class="card"><ng-content></ng-content></div>`
})
export class CardComponent {}// 使用
<card><h1>卡片标题</h1><p>卡片内容</p>
</card>
多插槽投影:
// 子组件
@Component({selector: 'card',template: `<div class="card"><header><ng-content select="[slot=header]"></ng-content></header><main><ng-content select="[slot=content]"></ng-content></main></div>`
})
export class CardComponent {}// 使用
<card><h1 slot="header">标题</h1><p slot="content">内容</p>
</card>
8. Angular中的变更检测机制是如何工作的?
答案: Angular使用Zone.js来监听异步操作,并触发变更检测。
变更检测过程:
- 触发源:用户事件、HTTP请求、定时器等
- Zone.js拦截:拦截异步操作
- 触发检测:从根组件开始检测
- 单向检测:从上到下检测组件树
- 更新DOM:更新变化的部分
优化策略:
// OnPush策略
@Component({changeDetection: ChangeDetectionStrategy.OnPush
})
export class OptimizedComponent {@Input() data: any;constructor(private cdr: ChangeDetectorRef) {}// 手动触发检测updateData() {this.cdr.markForCheck();}
}
脱离Zone:
export class MyComponent {constructor(private ngZone: NgZone) {}heavyTask() {this.ngZone.runOutsideAngular(() => {// 不触发变更检测的操作setInterval(() => {// 重任务}, 100);});}
}
指令篇
9. Angular中的指令类型有哪些?
答案:
指令分类:
1. 组件指令(Component Directives)
- 最复杂的指令类型
- 有自己的模板
- 继承自Directive
2. 属性指令(Attribute Directives)
- 改变元素外观或行为
- 不改变DOM结构
@Directive({selector: '[appHighlight]'
})
export class HighlightDirective {@Input() appHighlight: string;@HostListener('mouseenter') onMouseEnter() {this.highlight(this.appHighlight || 'yellow');}@HostListener('mouseleave') onMouseLeave() {this.highlight('');}private highlight(color: string) {this.el.nativeElement.style.backgroundColor = color;}constructor(private el: ElementRef) {}
}
3. 结构指令(Structural Directives)
- 改变DOM结构
- 使用*语法
@Directive({selector: '[appUnless]'
})
export class UnlessDirective {private hasView = false;@Input() set appUnless(condition: boolean) {if (!condition && !this.hasView) {this.viewContainer.createEmbeddedView(this.templateRef);this.hasView = true;} else if (condition && this.hasView) {this.viewContainer.clear();this.hasView = false;}}constructor(private templateRef: TemplateRef<any>,private viewContainer: ViewContainerRef) {}
}
10. 常用的内置指令有哪些?
答案:
结构指令:
<!-- *ngIf -->
<div *ngIf="isVisible; else elseTemplate">显示内容</div>
<ng-template #elseTemplate>隐藏时显示</ng-template><!-- *ngFor -->
<li *ngFor="let item of items; index as i; trackBy: trackByFn">{{i}}: {{item.name}}
</li><!-- *ngSwitch -->
<div [ngSwitch]="value"><p *ngSwitchCase="'A'">A</p><p *ngSwitchCase="'B'">B</p><p *ngSwitchDefault>Default</p>
</div>
属性指令:
<!-- ngClass -->
<div [ngClass]="{'active': isActive, 'disabled': isDisabled}"><!-- ngStyle -->
<div [ngStyle]="{'color': textColor, 'font-size': fontSize + 'px'}"><!-- ngModel -->
<input [(ngModel)]="inputValue">
服务与依赖注入篇
11. 什么是依赖注入?Angular是如何实现的?
答案: 依赖注入(DI)是一种设计模式,将依赖对象的创建和管理交给外部容器,而不是在类内部创建。
Angular DI特点:
- 分层注入器:模块级、组件级注入器
- 提供商(Providers):配置如何创建依赖
- 注入令牌(Tokens):标识依赖项
// 服务定义
@Injectable({providedIn: 'root' // 单例,应用级注入
})
export class DataService {constructor(private http: HttpClient) {}
}// 组件中注入
@Component({})
export class MyComponent {constructor(private dataService: DataService) {}
}// 自定义提供商
@NgModule({providers: [{ provide: API_URL, useValue: 'https://api.example.com' },{ provide: DataService, useClass: MockDataService },{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }]
})
export class AppModule {}
12. Provider的不同类型有哪些?
答案:
Provider类型:
1. useClass:
{ provide: DataService, useClass: DataService }
// 简写
DataService
2. useValue:
{ provide: API_CONFIG, useValue: { url: 'https://api.com', timeout: 5000 } }
3. useFactory:
{provide: DataService,useFactory: (http: HttpClient) => new DataService(http),deps: [HttpClient]
}
4. useExisting:
{ provide: NewService, useExisting: OldService }
5. multi: true:
{provide: HTTP_INTERCEPTORS,useClass: LoggingInterceptor,multi: true
}
13. 什么是注入令牌(Injection Token)?
答案: 注入令牌是用于标识依赖项的唯一标识符,特别适用于注入原始值或配置对象。
// 创建令牌
export const API_CONFIG = new InjectionToken<ApiConfig>('api.config');interface ApiConfig {url: string;timeout: number;
}// 提供值
@NgModule({providers: [{provide: API_CONFIG,useValue: {url: 'https://api.example.com',timeout: 5000}}]
})
export class AppModule {}// 注入使用
@Injectable()
export class ApiService {constructor(@Inject(API_CONFIG) private config: ApiConfig) {}getData() {console.log(this.config.url);}
}
模块篇
14. Angular模块系统是如何工作的?
答案: Angular模块(NgModule)是组织应用的方式,将相关的组件、指令、服务等打包在一起。
NgModule元数据:
@NgModule({declarations: [ // 声明:组件、指令、管道AppComponent,HeaderComponent],imports: [ // 导入:其他模块CommonModule,FormsModule,HttpClientModule],providers: [ // 提供商:服务DataService,{ provide: API_URL, useValue: 'https://api.com' }],exports: [ // 导出:可被其他模块使用HeaderComponent],bootstrap: [ // 启动组件(仅根模块)AppComponent]
})
export class AppModule {}
模块类型:
- 根模块(Root Module):AppModule,启动应用
- 特性模块(Feature Module):业务功能模块
- 共享模块(Shared Module):通用组件和服务
- 核心模块(Core Module):单例服务
15. 懒加载模块是如何实现的?
答案: 懒加载允许按需加载模块,减少初始包大小,提高应用启动速度。
路由配置:
const routes: Routes = [{path: 'admin',loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)},{path: 'user',loadChildren: () => import('./user/user.module').then(m => m.UserModule)}
];@NgModule({imports: [RouterModule.forRoot(routes)],exports: [RouterModule]
})
export class AppRoutingModule {}
特性模块:
@NgModule({declarations: [AdminComponent,UserListComponent],imports: [CommonModule,AdminRoutingModule]
})
export class AdminModule {}
预加载策略:
RouterModule.forRoot(routes, {preloadingStrategy: PreloadAllModules // 预加载所有懒加载模块
})
路由篇
16. Angular路由的核心概念有哪些?
答案:
核心概念:
- Routes:路由配置数组
- Router:导航服务
- ActivatedRoute:当前激活路由信息
- RouterOutlet:路由组件显示位置
- RouterLink:声明式导航
基本配置:
const routes: Routes = [{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },{ path: 'dashboard', component: DashboardComponent },{ path: 'users/:id', component: UserDetailComponent },{ path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) },{ path: '**', component: NotFoundComponent }
];
路由参数:
// 路径参数
export class UserDetailComponent {userId: string;constructor(private route: ActivatedRoute) {this.userId = this.route.snapshot.paramMap.get('id');// 响应式获取this.route.paramMap.subscribe(params => {this.userId = params.get('id');});}
}// 查询参数
this.router.navigate(['/users'], { queryParams: { page: 1, size: 10 } });
17. 路由守卫有哪些类型?
答案:
守卫类型:
1. CanActivate - 控制是否可以激活路由:
@Injectable()
export class AuthGuard implements CanActivate {constructor(private auth: AuthService, private router: Router) {}canActivate(): boolean {if (this.auth.isLoggedIn()) {return true;}this.router.navigate(['/login']);return false;}
}
2. CanActivateChild - 控制是否可以激活子路由:
@Injectable()
export class AdminGuard implements CanActivateChild {canActivateChild(): boolean {return this.checkAdminPermission();}
}
3. CanDeactivate - 控制是否可以离开路由:
@Injectable()
export class CanDeactivateGuard implements CanDeactivate<FormComponent> {canDeactivate(component: FormComponent): boolean {if (component.hasUnsavedChanges()) {return confirm('有未保存的更改,确定要离开吗?');}return true;}
}
4. Resolve - 预加载数据:
@Injectable()
export class UserResolve implements Resolve<User> {constructor(private userService: UserService) {}resolve(route: ActivatedRouteSnapshot): Observable<User> {return this.userService.getUser(route.paramMap.get('id'));}
}
路由配置:
{path: 'user/:id',component: UserComponent,canActivate: [AuthGuard],canDeactivate: [CanDeactivateGuard],resolve: { user: UserResolve }
}
表单篇
18. Angular中的表单类型有哪些?
答案:
两种表单类型:
1. 模板驱动表单(Template-driven Forms):
// 组件
export class TemplateFormComponent {user = { name: '', email: '' };onSubmit(form: NgForm) {if (form.valid) {console.log(this.user);}}
}// 模板
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)"><input name="name" [(ngModel)]="user.name" required #name="ngModel"><div *ngIf="name.invalid && name.touched">姓名必填</div><input name="email" [(ngModel)]="user.email" required email #email="ngModel"><div *ngIf="email.invalid && email.touched">邮箱格式错误</div><button [disabled]="userForm.invalid">提交</button>
</form>
2. 响应式表单(Reactive Forms):
export class ReactiveFormComponent {userForm: FormGroup;constructor(private fb: FormBuilder) {this.userForm = this.fb.group({name: ['', [Validators.required, Validators.minLength(2)]],email: ['', [Validators.required, Validators.email]],address: this.fb.group({street: [''],city: ['']})});}onSubmit() {if (this.userForm.valid) {console.log(this.userForm.value);}}get name() { return this.userForm.get('name'); }get email() { return this.userForm.get('email'); }
}// 模板
<form [formGroup]="userForm" (ngSubmit)="onSubmit()"><input formControlName="name"><div *ngIf="name?.invalid && name?.touched"><div *ngIf="name?.errors?.['required']">姓名必填</div><div *ngIf="name?.errors?.['minlength']">姓名至少2个字符</div></div><input formControlName="email"><div *ngIf="email?.invalid && email?.touched">邮箱格式错误</div><div formGroupName="address"><input formControlName="street" placeholder="街道"><input formControlName="city" placeholder="城市"></div><button [disabled]="userForm.invalid">提交</button>
</form>
19. 如何创建自定义验证器?
答案:
同步验证器:
// 自定义验证器函数
export function forbiddenNameValidator(forbiddenName: string): ValidatorFn {return (control: AbstractControl): ValidationErrors | null => {const forbidden = forbiddenName && control.value?.toLowerCase().includes(forbiddenName.toLowerCase());return forbidden ? { forbiddenName: { value: control.value } } : null;};
}// 使用
this.userForm = this.fb.group({name: ['', [Validators.required, forbiddenNameValidator('admin')]]
});
异步验证器:
export function uniqueEmailValidator(userService: UserService): AsyncValidatorFn {return (control: AbstractControl): Observable<ValidationErrors | null> => {if (!control.value) {return of(null);}return userService.checkEmailExists(control.value).pipe(map(exists => exists ? { emailExists: true } : null),catchError(() => of(null)));};
}// 使用
this.userForm = this.fb.group({email: ['', [Validators.required, Validators.email], [uniqueEmailValidator(this.userService)]]
});
跨字段验证器:
export const passwordMatchValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {const password = control.get('password');const confirmPassword = control.get('confirmPassword');return password && confirmPassword && password.value !== confirmPassword.value ? { passwordMismatch: true } : null;
};// 使用
this.userForm = this.fb.group({password: ['', Validators.required],confirmPassword: ['', Validators.required]
}, { validators: passwordMatchValidator });
HTTP篇
20. Angular中如何进行HTTP通信?
答案:
HttpClient基本使用:
@Injectable({providedIn: 'root'
})
export class ApiService {private baseUrl = 'https://api.example.com';constructor(private http: HttpClient) {}// GET请求getUsers(): Observable<User[]> {return this.http.get<User[]>(`${this.baseUrl}/users`);}// POST请求createUser(user: User): Observable<User> {return this.http.post<User>(`${this.baseUrl}/users`, user, {headers: new HttpHeaders({'Content-Type': 'application/json'})});}// PUT请求updateUser(id: number, user: User): Observable<User> {return this.http.put<User>(`${this.baseUrl}/users/${id}`, user);}// DELETE请求deleteUser(id: number): Observable<void> {return this.http.delete<void>(`${this.baseUrl}/users/${id}`);}// 带参数的GET请求getUsersWithParams(page: number, size: number): Observable<User[]> {const params = new HttpParams().set('page', page.toString()).set('size', size.toString());return this.http.get<User[]>(`${this.baseUrl}/users`, { params });}
}
错误处理:
export class ApiService {getUsers(): Observable<User[]> {return this.http.get<User[]>(`${this.baseUrl}/users`).pipe(retry(3), // 重试3次catchError(this.handleError));}private handleError(error: HttpErrorResponse): Observable<never> {let errorMessage = '';if (error.error instanceof ErrorEvent) {// 客户端错误errorMessage = `Error: ${error.error.message}`;} else {// 服务端错误errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;}console.error(errorMessage);return throwError(() => new Error(errorMessage));}
}
21. 什么是HTTP拦截器?如何使用?
答案: HTTP拦截器可以拦截HTTP请求和响应,用于添加通用逻辑如身份验证、日志记录、错误处理等。
身份验证拦截器:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {constructor(private auth: AuthService) {}intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {// 添加授权头const authToken = this.auth.getToken();if (authToken) {const authReq = req.clone({headers: req.headers.set('Authorization', `Bearer ${authToken}`)});return next.handle(authReq);}return next.handle(req);}
}
日志拦截器:
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {console.log('Request:', req.url);return next.handle(req).pipe(tap(event => {if (event instanceof HttpResponse) {console.log('Response:', event.status, event.body);}}));}
}
注册拦截器:
@NgModule({providers: [{provide: HTTP_INTERCEPTORS,useClass: AuthInterceptor,multi: true},{provide: HTTP_INTERCEPTORS,useClass: LoggingInterceptor,multi: true}]
})
export class AppModule {}
RxJS篇
22. 什么是Observable?与Promise的区别是什么?
答案: Observable是RxJS中表示异步数据流的核心概念。
Observable vs Promise:
特性 | Observable | Promise |
---|---|---|
执行时机 | 懒执行(订阅时执行) | 立即执行 |
值的数量 | 多个值 | 单个值 |
取消支持 | 支持取消 | 不支持取消 |
操作符 | 丰富的操作符 | 基本的then/catch |
错误处理 | 多种错误处理方式 | catch |
Observable示例:
// 创建Observable
const numbers$ = new Observable<number>(subscriber => {subscriber.next(1);subscriber.next(2);subscriber.next(3);subscriber.complete();
});// 订阅
const subscription = numbers$.subscribe({next: value => console.log(value),error: err => console.error(err),complete: () => console.log('Complete')
});// 取消订阅
subscription.unsubscribe();
23. 常用的RxJS操作符有哪些?
答案:
创建操作符:
// of - 创建发出指定值的Observable
const numbers$ = of(1, 2, 3, 4, 5);// from - 从数组、Promise等创建Observable
const fromArray$ = from([1, 2, 3]);
const fromPromise$ = from(fetch('/api/data'));// interval - 定时发出数值
const timer$ = interval(1000); // 每秒发出递增数字// fromEvent - 从DOM事件创建Observable
const clicks$ = fromEvent(document, 'click');
转换操作符:
// map - 转换值
const doubled$ = numbers$.pipe(map(x => x * 2)
);// mergeMap - 扁平化内部Observable
const searchResults$ = searchTerms$.pipe(mergeMap(term => this.searchService.search(term))
);// switchMap - 切换到新的Observable,取消之前的
const latestSearch$ = searchTerms$.pipe(switchMap(term => this.searchService.search(term))
);// concatMap - 按顺序连接Observable
const orderedResults$ = requests$.pipe(concatMap(request => this.processRequest(request))
);
过滤操作符:
// filter - 过滤值
const evenNumbers$ = numbers$.pipe(filter(x => x % 2 === 0)
);// distinctUntilChanged - 去重相邻重复值
const uniqueValues$ = values$.pipe(distinctUntilChanged()
);// debounceTime - 防抖
const searchInput$ = fromEvent(input, 'input').pipe(debounceTime(300),map(event => event.target.value)
);// throttleTime - 节流
const throttledClicks$ = clicks$.pipe(throttleTime(1000)
);
组合操作符:
// combineLatest - 组合最新值
const combined$ = combineLatest([firstName$,lastName$
]).pipe(map(([first, last]) => `${first} ${last}`)
);// merge - 合并多个Observable
const allEvents$ = merge(clicks$, keyPresses$, scrolls$);// zip - 配对值
const paired$ = zip(numbers$, letters$).pipe(map(([num, letter]) => `${num}${letter}`)
);
24. 如何处理内存泄漏和取消订阅?
答案:
手动取消订阅:
export class MyComponent implements OnInit, OnDestroy {private destroy$ = new Subject<void>();ngOnInit() {// 方法1:使用takeUntilthis.dataService.getData().pipe(takeUntil(this.destroy$)).subscribe(data => {console.log(data);});// 方法2:保存subscriptionthis.subscription = this.dataService.getData().subscribe(data => {console.log(data);});}ngOnDestroy() {// 方法1:发出销毁信号this.destroy$.next();this.destroy$.complete();// 方法2:手动取消订阅if (this.subscription) {this.subscription.unsubscribe();}}
}
使用async管道(推荐):
export class MyComponent {data$ = this.dataService.getData(); // 不需要手动取消订阅constructor(private dataService: DataService) {}
}// 模板中使用async管道
<div *ngIf="data$ | async as data">{{data.name}}
</div>
自定义操作符:
// 自动取消订阅的操作符
export function untilDestroyed(component: any) {return <T>(source: Observable<T>) => {const destroy$ = new Subject();const originalDestroy = component.ngOnDestroy;component.ngOnDestroy = function() {destroy$.next();destroy$.complete();if (originalDestroy) {originalDestroy.apply(this, arguments);}};return source.pipe(takeUntil(destroy$));};
}// 使用
this.dataService.getData().pipe(untilDestroyed(this)
).subscribe(data => console.log(data));
测试篇
25. Angular中的测试类型有哪些?
答案:
测试类型:
1. 单元测试(Unit Tests):
describe('UserService', () => {let service: UserService;let httpMock: HttpTestingController;beforeEach(() => {TestBed.configureTestingModule({imports: [HttpClientTestingModule],providers: [UserService]});service = TestBed.inject(UserService);httpMock = TestBed.inject(HttpTestingController);});it('should fetch users', () => {const mockUsers = [{ id: 1, name: 'John' }];service.getUsers().subscribe(users => {expect(users).toEqual(mockUsers);});const req = httpMock.expectOne('/api/users');expect(req.request.method).toBe('GET');req.flush(mockUsers);});afterEach(() => {httpMock.verify();});
});
2. 组件测试:
describe('UserComponent', () => {let component: UserComponent;let fixture: ComponentFixture<UserComponent>;let userService: jasmine.SpyObj<UserService>;beforeEach(() => {const spy = jasmine.createSpyObj('UserService', ['getUsers']);TestBed.configureTestingModule({declarations: [UserComponent],providers: [{ provide: UserService, useValue: spy }]});fixture = TestBed.createComponent(UserComponent);component = fixture.componentInstance;userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;});it('should display users', () => {const mockUsers = [{ id: 1, name: 'John' }];userService.getUsers.and.returnValue(of(mockUsers));fixture.detectChanges();const compiled = fixture.nativeElement;expect(compiled.querySelector('.user-name').textContent).toContain('John');});
});
3. 集成测试:
describe('App Integration', () => {it('should navigate to user detail', fakeAsync(() => {const fixture = TestBed.createComponent(AppComponent);const router = TestBed.inject(Router);const location = TestBed.inject(Location);fixture.detectChanges();router.navigate(['/users/1']);tick();expect(location.path()).toBe('/users/1');}));
});
26. 如何测试异步操作?
答案:
使用fakeAsync和tick:
it('should handle async operation', fakeAsync(() => {let result: string;setTimeout(() => {result = 'async result';}, 1000);tick(1000); // 模拟时间流逝expect(result).toBe('async result');
}));
测试Observable:
it('should handle observable', () => {const testData = { id: 1, name: 'Test' };service.getData().subscribe(data => {expect(data).toEqual(testData);});const req = httpMock.expectOne('/api/data');req.flush(testData);
});
使用done回调:
it('should handle promise', (done) => {service.getDataPromise().then(data => {expect(data).toBeTruthy();done();});
});
性能优化篇
27. Angular性能优化的策略有哪些?
答案:
变更检测优化:
// 1. OnPush变更检测策略
@Component({selector: 'app-user',changeDetection: ChangeDetectionStrategy.OnPush,template: `<div>{{user.name}}</div>`
})
export class UserComponent {@Input() user: User;
}// 2. 使用Immutable数据
updateUser(newName: string) {this.user = { ...this.user, name: newName }; // 创建新对象
}// 3. TrackBy函数优化*ngFor
trackByUserId(index: number, user: User): number {return user.id;
}// 模板中使用
<div *ngFor="let user of users; trackBy: trackByUserId">{{user.name}}
</div>
懒加载和预加载:
// 路由懒加载
const routes: Routes = [{path: 'feature',loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)}
];// 预加载策略
RouterModule.forRoot(routes, {preloadingStrategy: PreloadAllModules
})
代码分割和Tree Shaking:
// 动态导入
async loadUtility() {const { heavyUtility } = await import('./heavy-utility');return heavyUtility();
}// 使用具体导入
import { map, filter } from 'rxjs/operators'; // ✅ 好
import * as rxjs from 'rxjs'; // ❌ 不好
服务端渲染(SSR):
ng add @nguniversal/express-engine
npm run build:ssr
npm run serve:ssr
28. 如何优化大型列表的渲染性能?
答案:
虚拟滚动:
// 安装CDK
npm install @angular/cdk// 使用虚拟滚动
@Component({template: `<cdk-virtual-scroll-viewport itemSize="50" class="viewport"><div *cdkVirtualFor="let item of items; trackBy: trackByFn">{{item.name}}</div></cdk-virtual-scroll-viewport>`,styles: [`.viewport {height: 400px;width: 100%;}`]
})
export class VirtualScrollComponent {items = Array.from({length: 10000}, (_, i) => ({id: i, name: `Item ${i}`}));trackByFn(index: number, item: any) {return item.id;}
}
分页和无限滚动:
@Component({template: `<div *ngFor="let item of visibleItems">{{item.name}}</div><div #sentinel></div>`
})
export class InfiniteScrollComponent implements OnInit, AfterViewInit {@ViewChild('sentinel') sentinel: ElementRef;allItems: Item[] = [];visibleItems: Item[] = [];private page = 0;private pageSize = 20;ngAfterViewInit() {const observer = new IntersectionObserver(entries => {if (entries[0].isIntersecting) {this.loadMore();}});observer.observe(this.sentinel.nativeElement);}loadMore() {const start = this.page * this.pageSize;const end = start + this.pageSize;this.visibleItems = [...this.visibleItems, ...this.allItems.slice(start, end)];this.page++;}
}
状态管理篇
29. Angular中有哪些状态管理方案?
答案:
1. 服务 + BehaviorSubject:
@Injectable({providedIn: 'root'
})
export class StateService {private userSubject = new BehaviorSubject<User | null>(null);user$ = this.userSubject.asObservable();updateUser(user: User) {this.userSubject.next(user);}getUser(): User | null {return this.userSubject.value;}
}
2. NgRx:
// Actions
export const loadUsers = createAction('[User] Load Users');
export const loadUsersSuccess = createAction('[User] Load Users Success',props<{ users: User[] }>()
);// Reducer
const userReducer = createReducer(initialState,on(loadUsers, state => ({ ...state, loading: true })),on(loadUsersSuccess, (state, { users }) => ({...state,users,loading: false}))
);// Effects
@Injectable()
export class UserEffects {loadUsers$ = createEffect(() =>this.actions$.pipe(ofType(loadUsers),switchMap(() =>this.userService.getUsers().pipe(map(users => loadUsersSuccess({ users }))))));constructor(private actions$: Actions,private userService: UserService) {}
}// Selectors
export const selectUsers = createSelector(selectUserState,state => state.users
);// 组件中使用
@Component({})
export class UserListComponent {users$ = this.store.select(selectUsers);constructor(private store: Store) {}loadUsers() {this.store.dispatch(loadUsers());}
}
3. Akita:
// Store
export interface UserState extends EntityState<User> {loading: boolean;
}@Injectable({ providedIn: 'root' })
@StoreConfig({ name: 'user' })
export class UserStore extends EntityStore<UserState> {constructor() {super({ loading: false });}
}// Query
@Injectable({ providedIn: 'root' })
export class UserQuery extends QueryEntity<UserState> {loading$ = this.select('loading');constructor(protected store: UserStore) {super(store);}
}// Service
@Injectable({ providedIn: 'root' })
export class UserService {constructor(private userStore: UserStore,private http: HttpClient) {}getUsers() {this.userStore.setLoading(true);return this.http.get<User[]>('/api/users').pipe(tap(users => {this.userStore.set(users);this.userStore.setLoading(false);}));}
}
30. 如何在Angular中实现缓存?
答案:
HTTP缓存:
@Injectable()
export class CacheInterceptor implements HttpInterceptor {private cache = new Map<string, HttpResponse<any>>();intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {if (req.method === 'GET') {const cachedResponse = this.cache.get(req.url);if (cachedResponse) {return of(cachedResponse);}}return next.handle(req).pipe(tap(event => {if (event instanceof HttpResponse && req.method === 'GET') {this.cache.set(req.url, event);}}));}
}
内存缓存服务:
@Injectable({providedIn: 'root'
})
export class CacheService {private cache = new Map<string, { data: any; timestamp: number; ttl: number }>();set(key: string, data: any, ttl: number = 300000): void { // 5分钟TTLthis.cache.set(key, {data,timestamp: Date.now(),ttl});}get(key: string): any | null {const cached = this.cache.get(key);if (!cached) {return null;}if (Date.now() - cached.timestamp > cached.ttl) {this.cache.delete(key);return null;}return cached.data;}clear(): void {this.cache.clear();}
}// 在服务中使用缓存
@Injectable()
export class DataService {constructor(private http: HttpClient,private cache: CacheService) {}getData(id: string): Observable<any> {const cacheKey = `data_${id}`;const cached = this.cache.get(cacheKey);if (cached) {return of(cached);}return this.http.get(`/api/data/${id}`).pipe(tap(data => this.cache.set(cacheKey, data)));}
}
安全篇
31. Angular中的安全防护措施有哪些?
答案:
XSS防护:
// Angular自动转义HTML
@Component({template: `<div>{{userInput}}</div> <!-- 自动转义 --><div [innerHTML]="trustedHtml"></div> <!-- 需要信任的HTML -->`
})
export class SafeComponent {userInput = '<script>alert("xss")</script>'; // 会被转义constructor(private sanitizer: DomSanitizer) {}get trustedHtml() {return this.sanitizer.bypassSecurityTrustHtml(this.someHtml);}
}
CSRF防护:
// HttpClientXsrfModule配置
@NgModule({imports: [HttpClientModule,HttpClientXsrfModule.withOptions({cookieName: 'XSRF-TOKEN',headerName: 'X-XSRF-TOKEN'})]
})
export class AppModule {}
内容安全策略(CSP):
<!-- index.html -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-eval';">
JWT Token管理:
@Injectable()
export class AuthService {private tokenKey = 'auth_token';setToken(token: string): void {localStorage.setItem(this.tokenKey, token);}getToken(): string | null {return localStorage.getItem(this.tokenKey);}removeToken(): void {localStorage.removeItem(this.tokenKey);}isTokenExpired(): boolean {const token = this.getToken();if (!token) return true;const payload = JSON.parse(atob(token.split('.')[1]));return Date.now() >= payload.exp * 1000;}
}
32. 如何实现角色基础的访问控制?
答案:
权限守卫:
export enum Role {Admin = 'admin',User = 'user',Guest = 'guest'
}@Injectable()
export class RoleGuard implements CanActivate {constructor(private auth: AuthService,private router: Router) {}canActivate(route: ActivatedRouteSnapshot): boolean {const requiredRoles = route.data['roles'] as Role[];const userRole = this.auth.getUserRole();if (requiredRoles && !requiredRoles.includes(userRole)) {this.router.navigate(['/unauthorized']);return false;}return true;}
}// 路由配置
{path: 'admin',component: AdminComponent,canActivate: [RoleGuard],data: { roles: [Role.Admin] }
}
权限指令:
@Directive({selector: '[hasRole]'
})
export class HasRoleDirective implements OnInit {@Input() hasRole: Role[];constructor(private templateRef: TemplateRef<any>,private viewContainer: ViewContainerRef,private auth: AuthService) {}ngOnInit() {const userRole = this.auth.getUserRole();if (this.hasRole.includes(userRole)) {this.viewContainer.createEmbeddedView(this.templateRef);} else {this.viewContainer.clear();}}
}// 使用
<button *hasRole="[Role.Admin]">删除用户</button>
高级特性篇
33. Angular中的动态组件如何实现?
答案:
使用ComponentFactoryResolver(Angular 13之前):
@Component({template: `<div #container></div>`
})
export class DynamicHostComponent {@ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;constructor(private componentFactoryResolver: ComponentFactoryResolver) {}loadComponent(componentType: any) {const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType);this.container.clear();const componentRef = this.container.createComponent(componentFactory);// 设置输入属性componentRef.instance.data = 'some data';return componentRef;}
}
使用ViewContainerRef.createComponent(Angular 13+):
@Component({template: `<div #container></div>`
})
export class DynamicHostComponent {@ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;loadComponent(componentType: any) {this.container.clear();const componentRef = this.container.createComponent(componentType);componentRef.setInput('data', 'some data');return componentRef;}
}
动态表单示例:
interface FieldConfig {type: 'input' | 'select' | 'checkbox';name: string;label: string;options?: string[];
}@Component({template: `<form [formGroup]="form"><div #fieldContainer></div></form>`
})
export class DynamicFormComponent implements OnInit {@ViewChild('fieldContainer', { read: ViewContainerRef }) container: ViewContainerRef;@Input() fields: FieldConfig[] = [];form: FormGroup;constructor(private fb: FormBuilder) {}ngOnInit() {this.buildForm();this.loadFields();}private buildForm() {const group: any = {};this.fields.forEach(field => {group[field.name] = [''];});this.form = this.fb.group(group);}private loadFields() {this.fields.forEach(field => {const component = this.getComponentType(field.type);const componentRef = this.container.createComponent(component);componentRef.setInput('config', field);componentRef.setInput('formControl', this.form.get(field.name));});}private getComponentType(type: string) {switch (type) {case 'input': return InputFieldComponent;case 'select': return SelectFieldComponent;case 'checkbox': return CheckboxFieldComponent;default: return InputFieldComponent;}}
}
34. 什么是Angular Elements?
答案: Angular Elements允许将Angular组件转换为自定义元素(Custom Elements),可以在任何HTML页面中使用。
创建Angular Element:
// 安装
npm install @angular/elements// 创建自定义元素
import { createCustomElement } from '@angular/elements';@Component({selector: 'app-hello',template: `<h1>Hello {{name}}!</h1>`,encapsulation: ViewEncapsulation.ShadowDom
})
export class HelloComponent {@Input() name = 'World';
}@NgModule({declarations: [HelloComponent],imports: [BrowserModule],entryComponents: [HelloComponent] // Angular 9之前需要
})
export class AppModule {constructor(private injector: Injector) {}ngDoBootstrap() {const helloElement = createCustomElement(HelloComponent, { injector: this.injector });customElements.define('hello-element', helloElement);}
}
使用自定义元素:
<!-- 在任何HTML页面中使用 -->
<hello-element name="Angular"></hello-element><script>// 动态设置属性const element = document.querySelector('hello-element');element.name = 'Dynamic Name';
</script>
35. Angular中的微前端架构如何实现?
答案:
使用Module Federation:
// webpack.config.js (子应用)
const ModuleFederationPlugin = require('@module-federation/webpack');module.exports = {plugins: [new ModuleFederationPlugin({name: 'mfe1',filename: 'remoteEntry.js',exposes: {'./Module': './src/app/feature/feature.module.ts'},shared: {'@angular/core': { singleton: true },'@angular/common': { singleton: true },'@angular/router': { singleton: true }}})]
};// webpack.config.js (主应用)
new ModuleFederationPlugin({name: 'shell',remotes: {mfe1: 'mfe1@http://localhost:4201/remoteEntry.js'},shared: {'@angular/core': { singleton: true },'@angular/common': { singleton: true },'@angular/router': { singleton: true }}
})
路由配置:
// 主应用路由
const routes: Routes = [{path: 'mfe1',loadChildren: () => import('mfe1/Module').then(m => m.FeatureModule)}
];
Single-SPA方案:
// 子应用入口
import { NgZone } from '@angular/core';
import { Router } from '@angular/router';
import { singleSpaAngular, getSingleSpaExtraProviders } from 'single-spa-angular';const lifecycles = singleSpaAngular({bootstrapFunction: singleSpaProps => {return platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(AppModule);},template: '<app-root />',Router,NgZone
});export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;
最佳实践篇
36. Angular项目的文件结构最佳实践
答案:
推荐的项目结构:
src/
├── app/
│ ├── core/ # 核心模块(单例服务)
│ │ ├── guards/
│ │ ├── interceptors/
│ │ ├── services/
│ │ └── core.module.ts
│ ├── shared/ # 共享模块
│ │ ├── components/
│ │ ├── directives/
│ │ ├── pipes/
│ │ └── shared.module.ts
│ ├── features/ # 特性模块
│ │ ├── user/
│ │ │ ├── components/
│ │ │ ├── services/
│ │ │ ├── user-routing.module.ts
│ │ │ └── user.module.ts
│ ├── layouts/ # 布局组件
│ ├── app-routing.module.ts
│ ├── app.component.ts
│ └── app.module.ts
├── assets/ # 静态资源
│ ├── images/
│ ├── icons/
│ └── i18n/
├── environments/ # 环境配置
└── styles/ # 全局样式├── _variables.scss├── _mixins.scss└── styles.scss
模块组织原则:
- Core Module:放置单例服务,只被AppModule导入
- Shared Module:放置通用组件、指令、管道
- Feature Module:按业务功能组织,可懒加载
- Layout Module:布局相关组件
37. Angular编码规范和最佳实践
答案:
命名规范:
// 1. 使用kebab-case命名文件
user-detail.component.ts
user.service.ts
auth.guard.ts// 2. 类名使用PascalCase
export class UserDetailComponent {}
export class UserService {}
export class AuthGuard {}// 3. 变量和方法使用camelCase
userName: string;
getUserData(): void {}// 4. 常量使用SCREAMING_SNAKE_CASE
export const API_BASE_URL = 'https://api.example.com';
组件设计原则:
// 1. 单一职责原则
@Component({selector: 'app-user-list', // 只负责用户列表显示template: `<div *ngFor="let user of users"><app-user-item [user]="user" (delete)="onDelete($event)"></app-user-item></div>`
})
export class UserListComponent {@Input() users: User[];@Output() delete = new EventEmitter<number>();onDelete(userId: number) {this.delete.emit(userId);}
}// 2. 使用OnPush策略提高性能
@Component({changeDetection: ChangeDetectionStrategy.OnPush
})
export class OptimizedComponent {}// 3. 实现生命周期接口
export class MyComponent implements OnInit, OnDestroy {ngOnInit(): void {}ngOnDestroy(): void {}
}// 4. 使用trackBy优化*ngFor
trackByUserId = (index: number, user: User) => user.id;
服务设计原则:
// 1. 使用依赖注入
@Injectable({providedIn: 'root' // 单例服务
})
export class UserService {constructor(private http: HttpClient) {}
}// 2. 返回Observable而不是Promise
getUserById(id: number): Observable<User> {return this.http.get<User>(`/api/users/${id}`);
}// 3. 错误处理
getUserById(id: number): Observable<User> {return this.http.get<User>(`/api/users/${id}`).pipe(retry(3),catchError(this.handleError));
}private handleError(error: HttpErrorResponse): Observable<never> {console.error('An error occurred:', error.error);return throwError(() => new Error('Something went wrong'));
}
模板最佳实践:
<!-- 1. 使用async管道 -->
<div *ngIf="user$ | async as user">{{user.name}}
</div><!-- 2. 避免在模板中调用方法 -->
<!-- 不好 -->
<div>{{getFullName()}}</div><!-- 好 -->
<div>{{fullName}}</div><!-- 3. 使用trackBy提高性能 -->
<div *ngFor="let item of items; trackBy: trackByFn">{{item.name}}
</div><!-- 4. 合理使用*ngIf和hidden -->
<!-- 频繁切换使用hidden -->
<div [hidden]="!isVisible">Content</div><!-- 不频繁切换使用*ngIf -->
<div *ngIf="isVisible">Content</div>
38. 如何进行Angular应用的SEO优化?
答案:
服务端渲染(SSR):
# 添加Angular Universal
ng add @nguniversal/express-engine# 构建和运行SSR
npm run build:ssr
npm run serve:ssr
预渲染(Prerendering):
# 添加预渲染功能
ng add @nguniversal/express-engine# 构建预渲染版本
npm run prerender
Meta标签管理:
import { Meta, Title } from '@angular/platform-browser';@Component({})
export class ProductDetailComponent implements OnInit {constructor(private meta: Meta,private title: Title,private route: ActivatedRoute) {}ngOnInit() {this.route.data.subscribe(data => {const product = data.product;// 设置页面标题this.title.setTitle(`${product.name} - 商品详情`);// 设置Meta标签this.meta.updateTag({ name: 'description', content: product.description });this.meta.updateTag({ name: 'keywords', content: product.keywords });// Open Graph标签this.meta.updateTag({ property: 'og:title', content: product.name });this.meta.updateTag({ property: 'og:description', content: product.description });this.meta.updateTag({ property: 'og:image', content: product.image });// Twitter Card标签this.meta.updateTag({ name: 'twitter:card', content: 'summary_large_image' });this.meta.updateTag({ name: 'twitter:title', content: product.name });});}
}
结构化数据:
@Injectable()
export class SeoService {constructor(@Inject(DOCUMENT) private document: Document) {}addStructuredData(data: any) {const script = this.document.createElement('script');script.type = 'application/ld+json';script.text = JSON.stringify(data);this.document.head.appendChild(script);}addProductStructuredData(product: Product) {const structuredData = {'@context': 'https://schema.org/','@type': 'Product','name': product.name,'description': product.description,'image': product.image,'offers': {'@type': 'Offer','price': product.price,'priceCurrency': 'CNY'}};this.addStructuredData(structuredData);}
}
39. Angular国际化(i18n)如何实现?
答案:
配置国际化:
# 添加国际化支持
ng add @angular/localize# 提取文本
ng extract-i18n# 构建不同语言版本
ng build --localize
标记需要翻译的文本:
<!-- 使用i18n属性 -->
<h1 i18n="@@welcome">欢迎</h1><!-- 带描述和含义 -->
<p i18n="user.greeting|问候用户">你好,{{userName}}</p><!-- 复数形式 -->
<span i18n>{count, plural, =0 {没有消息} =1 {1条消息} other {{{count}}条消息}}</span><!-- ICU表达式 -->
<span i18n>{gender, select, male {他} female {她} other {它}}</span>
在组件中使用:
import { LOCALE_ID, Inject } from '@angular/core';@Component({})
export class MyComponent {constructor(@Inject(LOCALE_ID) public locale: string) {}// 使用$localize标记字符串message = $localize`欢迎使用我们的应用`;// 带插值的本地化greetUser(name: string) {return $localize`你好,${name}`;}
}
日期和数字管道:
<!-- 日期本地化 -->
<p>{{ today | date:'medium':locale }}</p><!-- 数字本地化 -->
<p>{{ price | currency:'CNY':'symbol':'1.2-2':locale }}</p><!-- 百分比 -->
<p>{{ ratio | percent:'1.1-1':locale }}</p>
动态切换语言:
@Injectable()
export class LocaleService {private currentLocale = 'zh-CN';setLocale(locale: string) {this.currentLocale = locale;// 重新加载应用或动态加载翻译文件window.location.reload();}getCurrentLocale(): string {return this.currentLocale;}
}
40. Angular应用的部署策略有哪些?
答案:
构建优化:
# 生产环境构建
ng build --prod# 启用AOT编译
ng build --aot# 分析包大小
ng build --prod --source-map
npm install -g webpack-bundle-analyzer
webpack-bundle-analyzer dist/*/main.js
部署配置文件:
// angular.json
{"projects": {"my-app": {"architect": {"build": {"configurations": {"production": {"fileReplacements": [{"replace": "src/environments/environment.ts","with": "src/environments/environment.prod.ts"}],"optimization": true,"outputHashing": "all","sourceMap": false,"extractCss": true,"namedChunks": false,"extractLicenses": true,"vendorChunk": false,"buildOptimizer": true,"budgets": [{"type": "initial","maximumWarning": "2mb","maximumError": "5mb"}]}}}}}}
}
Nginx配置示例:
server {listen 80;server_name your-domain.com;root /var/www/your-app/dist;index index.html;# 处理Angular路由location / {try_files $uri $uri/ /index.html;}# 静态资源缓存location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {expires 1y;add_header Cache-Control "public, immutable";}# 压缩gzip on;gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}
Docker部署:
# 多阶段构建
FROM node:16-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build --prodFROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
CI/CD流水线(GitHub Actions示例):
name: Deploy Angular Appon:push:branches: [main]jobs:build-and-deploy:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v2- name: Setup Node.jsuses: actions/setup-node@v2with:node-version: '16'cache: 'npm'- name: Install dependenciesrun: npm ci- name: Run testsrun: npm run test -- --watch=false --browsers=ChromeHeadless- name: Buildrun: npm run build --prod- name: Deploy to S3run: aws s3 sync dist/ s3://your-bucket-name --deleteenv:AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
面试技巧和项目经验篇
41. 常见的Angular面试陷阱题
问题1:为什么需要使用OnPush变更检测策略? 答案: 默认的变更检测策略会检查所有组件,OnPush策略只在以下情况触发检测:
- @Input属性引用发生变化
- 事件触发(DOM事件、@Output事件)
- 手动调用ChangeDetectorRef.markForCheck()
- Observable发出新值(使用async管道)
问题2:Angular中的单例服务是如何保证的? 答案: 通过providedIn: 'root'或在根模块的providers中注册,Angular的依赖注入系统会为整个应用创建唯一实例。子模块中重复提供会创建新实例。
问题3:ngFor和ngIf能同时使用吗? 答案: 不能在同一个元素上同时使用。解决方案:
<!-- 使用ng-container -->
<ng-container *ngFor="let item of items"><div *ngIf="item.visible">{{item.name}}</div>
</ng-container><!-- 或者使用管道过滤 -->
<div *ngFor="let item of items | filter:isVisible">{{item.name}}</div>
42. 项目经验相关问题的回答框架
问题:描述一个你在Angular项目中遇到的性能问题及解决方案
回答框架:
- 背景描述:项目规模、遇到的具体性能问题
- 问题分析:使用的分析工具(Chrome DevTools、Angular DevTools)
- 解决方案:具体采用的优化策略
- 效果评估:优化前后的性能对比数据
- 经验总结:从中学到的经验和最佳实践
示例回答: "在一个电商项目中,商品列表页面在显示1000+商品时出现卡顿。通过Chrome DevTools分析发现变更检测耗时过长。我采用了以下优化方案:
- 使用OnPush变更检测策略
- 实现虚拟滚动(CDK Virtual Scrolling)
- 添加trackBy函数优化*ngFor
- 使用异步管道避免手动订阅 最终页面渲染时间从3秒降至500ms,用户体验显著提升。"
问题:如何设计一个可复用的Angular组件库?
回答要点:
- API设计:简洁明了的输入输出接口
- 主题定制:支持CSS变量和主题切换
- 无障碍访问:遵循ARIA标准
- 文档和示例:详细的使用文档和在线示例
- 测试覆盖:单元测试和端到端测试
- 版本管理:语义化版本控制
- TypeScript支持:完整的类型定义
43. Angular新特性和发展趋势
Angular 15+ 新特性:
- Standalone Components:独立组件,减少模块样板代码
- Optional Injectors:可选的依赖注入
- Directive Composition API:指令组合
- Image Optimization:图片优化指令
- Angular CLI Auto-completion:CLI自动补全
Standalone Components示例:
@Component({selector: 'app-standalone',standalone: true,imports: [CommonModule, FormsModule],template: `<div>Standalone Component</div>`
})
export class StandaloneComponent {}// 引导应用
bootstrapApplication(StandaloneComponent, {providers: [importProvidersFrom(BrowserModule),provideRouter(routes)]
});
未来发展趋势:
- 更好的性能:Ivy渲染引擎持续优化
- 更小的包体积:Tree-shaking和代码分割
- 更好的开发体验:更强的TypeScript集成
- Web Components:更好的自定义元素支持
- 微前端:Module Federation集成
总结
以上43个问题涵盖了Angular的核心概念、高级特性、最佳实践等各个方面。在准备面试时,建议:
- 深入理解原理:不仅要知道怎么用,更要知道为什么这样设计
- 结合实际项目:用具体的项目经验来回答问题
- 关注新特性:了解Angular的最新发展和趋势
- 练习代码实现:能够手写核心功能的简单实现
- 准备问题反问:准备一些有深度的问题向面试官提问
记住,面试不仅是展示技术能力,也是展示解决问题的思路和学习能力的机会。