Angular面试题目和答案大全

发布于:2025-07-30 ⋅ 阅读:(22) ⋅ 点赞:(0)

基础概念篇

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组件的生命周期钩子有哪些?

答案:

生命周期顺序:

  1. ngOnChanges:输入属性变化时调用
  2. ngOnInit:初始化时调用(一次)
  3. ngDoCheck:每次变更检测时调用
  4. ngAfterContentInit:内容投影初始化后调用(一次)
  5. ngAfterContentChecked:每次检查投影内容后调用
  6. ngAfterViewInit:视图初始化后调用(一次)
  7. ngAfterViewChecked:每次检查视图后调用
  8. 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来监听异步操作,并触发变更检测。

变更检测过程:

  1. 触发源:用户事件、HTTP请求、定时器等
  2. Zone.js拦截:拦截异步操作
  3. 触发检测:从根组件开始检测
  4. 单向检测:从上到下检测组件树
  5. 更新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:使用takeUntil
    this.dataService.getData().pipe(
      takeUntil(this.destroy$)
    ).subscribe(data => {
      console.log(data);
    });
    
    // 方法2:保存subscription
    this.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分钟TTL
    this.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 --prod

FROM 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 App

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Setup Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '16'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run tests
      run: npm run test -- --watch=false --browsers=ChromeHeadless
    
    - name: Build
      run: npm run build --prod
    
    - name: Deploy to S3
      run: aws s3 sync dist/ s3://your-bucket-name --delete
      env:
        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项目中遇到的性能问题及解决方案

回答框架:

  1. 背景描述:项目规模、遇到的具体性能问题
  2. 问题分析:使用的分析工具(Chrome DevTools、Angular DevTools)
  3. 解决方案:具体采用的优化策略
  4. 效果评估:优化前后的性能对比数据
  5. 经验总结:从中学到的经验和最佳实践

示例回答: "在一个电商项目中,商品列表页面在显示1000+商品时出现卡顿。通过Chrome DevTools分析发现变更检测耗时过长。我采用了以下优化方案:

  1. 使用OnPush变更检测策略
  2. 实现虚拟滚动(CDK Virtual Scrolling)
  3. 添加trackBy函数优化*ngFor
  4. 使用异步管道避免手动订阅 最终页面渲染时间从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的核心概念、高级特性、最佳实践等各个方面。在准备面试时,建议:

  1. 深入理解原理:不仅要知道怎么用,更要知道为什么这样设计
  2. 结合实际项目:用具体的项目经验来回答问题
  3. 关注新特性:了解Angular的最新发展和趋势
  4. 练习代码实现:能够手写核心功能的简单实现
  5. 准备问题反问:准备一些有深度的问题向面试官提问

记住,面试不仅是展示技术能力,也是展示解决问题的思路和学习能力的机会。


网站公告

今日签到

点亮在社区的每一天
去签到