基礎概念篇
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的最新發展和趨勢
- 練習代碼實現:能夠手寫核心功能的簡單實現
- 準備問題反問:準備一些有深度的問題向面試官提問
記住,面試不僅是展示技術能力,也是展示解決問題的思路和學習能力的機會。