Article

前端框架 Angular

更新于:2026-07-10

一、核心基础概念

1. 组件(Component)

@Component 装饰器

用于定义 Angular 组件及其元数据。

selector 必须唯一;templateUrltemplate 二选一;组件类必须用 export 导出。

import { Component } from '@angular/core';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class UserComponent { }

组件类定义

封装组件逻辑与数据。类名应使用 PascalCase;需在模块中声明。

export class ProductComponent {
  title = 'Product List';
}

2. 模板语法

插值表达式 {{ expression }}

在模板中显示组件数据。只能输出字符串或可转为字符串的值;避免复杂逻辑。

<h1>{{ title }}</h1>
<p>1 + 1 = {{ 1 + 1 }}</p>

属性绑定 [property]="expression"

将 DOM 属性绑定到组件属性。使用中括号 [];绑定值为表达式,非字符串。

<img [src]="imageUrl" />
<button [disabled]="isDisabled">Click</button>

事件绑定 (event)="handler($event)"

响应用户操作事件。$event 可传递原生事件对象;处理函数需在组件中定义。

<button (click)="onClick()">Submit</button>

双向绑定 [(ngModel)]="property"

实现数据与表单元素的双向同步。需导入 FormsModule;常用于表单输入控件。

<input [(ngModel)]="name" placeholder="Enter name" />

3. 组件生命周期钩子

ngOnInit

初始化组件,加载数据。最常用的生命周期钩子;适合发起 HTTP 请求。

ngOnInit() {
  this.loadUsers();
}

ngOnChanges

监听输入属性变化。接收 SimpleChanges 对象;首次创建也会触发。

ngOnChanges(changes: SimpleChanges) {
  if (changes['userId']) {
    this.fetchUser();
  }
}

ngAfterViewInit

视图初始化完成后执行。可安全访问 @ViewChild 查询的元素。

ngAfterViewInit() {
  console.log('View is ready');
}

ngOnDestroy

组件销毁前清理资源。必须清理订阅、定时器等,防止内存泄漏。

ngOnDestroy() {
  this.subscription.unsubscribe();
}

4. 组件输入输出(@Input / @Output)

@Input()

接收父组件传递的数据。属性必须在父组件模板中通过 [property] 绑定。

@Input() userName: string;
@Input('alias') age: number;

@Output() + EventEmitter

向父组件发射事件。必须配合 EventEmitter 使用;父组件用 (event) 监听。

@Output() userAdded = new EventEmitter<string>();

addUser() {
  this.userAdded.emit(this.newUser);
}

5. 模块(Module)与 @NgModule

@NgModule 装饰器

定义模块及其配置。每个应用至少有一个根模块(AppModule)。

@NgModule({
  declarations: [AppComponent, UserComponent],
  imports: [BrowserModule, FormsModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
属性说明注意事项
declarations声明本模块拥有的视图类只能声明组件、指令、管道;不能重复声明
imports引入其他模块的功能引入的模块需已安装并导出所需功能
providers提供依赖注入服务服务在模块级注册时为单例(除非惰性加载)
exports导出供其他模块使用的内容其他模块 import 后才能使用导出内容

6. 惰性加载模块(Lazy Loading)

路由配置惰性加载

按需加载模块,提升首屏性能。必须使用 import() 函数;模块不能在 AppModule 的 imports 中重复导入。

const routes: Routes = [
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
  }
];

特性模块(Feature Module)

封装特定功能的独立模块。惰性加载模块必须是特性模块,不能是根模块。

@NgModule({
  declarations: [AdminComponent],
  imports: [CommonModule]
})
export class AdminModule { }

7. 模板与数据绑定(补充)

模板引用变量 #varName

引用 DOM 元素或组件。变量作用域限于模板;避免过度使用。

<input #searchInput />
<button (click)="search(searchInput.value)">Search</button>

安全导航操作符 ?.

防止访问 nullundefined 属性时报错。用于可能为空的对象链,避免模板崩溃。

<p>{{ user?.profile?.email }}</p>

非空断言操作符 !

告诉 TypeScript 编译器该值不为 nullundefined。仅在确定不为空时使用,否则运行时报错。

@ViewChild('container') container!: ElementRef;

8. 指令(Directive)

*ngIf

条件渲染元素。元素从 DOM 中添加或移除;可配合 else 使用。

<div *ngIf="isLoggedIn">Welcome!</div>

*ngFor

遍历数组渲染列表。推荐使用 trackBy 提升性能。

<li *ngFor="let user of users; index as i">
  {{ i + 1 }}. {{ user.name }}
</li>
// 配合 trackBy 使用
trackBy: trackById

*ngSwitch

多分支条件渲染。适用于多个固定值判断场景。

<div [ngSwitch]="color">
  <p *ngSwitchCase="'red'">Red</p>
  <p *ngSwitchCase="'blue'">Blue</p>
  <p *ngSwitchDefault>Other</p>
</div>

ngClass

动态添加/移除 CSS 类。支持对象、数组、字符串形式。

<div [ngClass]="{ 'active': isActive, 'disabled': isDisabled }"></div>

ngStyle

动态设置内联样式。样式属性名使用驼峰或引号包裹。

<div [ngStyle]="{ 'color': textColor, 'font-size': size + 'px' }"></div>

二、状态管理与通信

1. 服务与依赖注入(Service & DI)

@Injectable() 装饰器

标记类为可注入的服务,并配置提供方式。必须添加装饰器才能被注入;providedIn: 'root' 表示根级单例。

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DataService {
  getData() { return []; }
}

构造函数注入

在组件或服务中注入依赖。Angular DI 系统自动解析依赖;类型即令牌。

constructor(private dataService: DataService) {
  this.data = this.dataService.getData();
}

模块级注册

在模块中提供服务(非根级)。若模块被多次导入,可能导致多个实例。

@NgModule({
  providers: [AuthService]
})
export class AdminModule { }

注入器层级

级别方式说明
根级注入器@Injectable({ providedIn: 'root' })全局单例服务,Tree-shakable,推荐方式
模块级注入@NgModule({ providers: [...] })服务作用域限定于模块,懒加载时创建独立实例
组件级注入providers: [...] in @Component服务作用域限定于组件及子组件,每次创建组件都会新建服务实例

单例服务

确保服务全局唯一实例。推荐用于状态共享、日志、HTTP 封装等。

@Injectable({ providedIn: 'root' })
export class MessageService {
  messages: string[] = [];
}

多例服务

在组件中通过 providers 注册,创建多个独立实例。适用于需要隔离状态的场景。

@Component({
  providers: [CounterService]
})

2. 组件间通信

父子组件通信(Input / Output)

// 子组件 - 接收数据
@Input() title: string;
@Input('userId') id: number;

// 子组件 - 发射事件
@Output() saved = new EventEmitter<User>();

onSave() {
  this.saved.emit(this.user);
}

父组件使用 (eventName)="handler($event)" 监听子组件事件:

<child-comp (saved)="onUserSaved($event)"></child-comp>

非父子组件通信(通过服务共享数据)

使用 BehaviorSubject 实现响应式数据流:

@Injectable({ providedIn: 'root' })
export class SharedService {
  private messageSource = new BehaviorSubject<string>('');
  currentMessage = this.messageSource.asObservable();

  changeMessage(message: string) {
    this.messageSource.next(message);
  }
}

组件订阅共享数据变化。注意:订阅后必须在 ngOnDestroy 中取消订阅。

this.sharedService.currentMessage.subscribe(message => {
  this.message = message;
});

使用 EventEmitter 触发自定义事件

// 子组件中定义
@Output() itemAdded = new EventEmitter<string>();

addItem(name: string) {
  this.itemAdded.emit(name);
}

父组件监听:

<child-comp (itemAdded)="onAdd($event)"></child-comp>

视图子元素访问(@ViewChild / @ViewChildren)

@ViewChild() 获取模板中元素、组件或指令引用。在 ngAfterViewInit 钩子后才可用。

@ViewChild('input') inputEl!: ElementRef;

focusInput() {
  this.inputEl.nativeElement.focus();
}

获取子组件实例,可调用其方法或访问其属性:

@ViewChild(UserFormComponent) form!: UserFormComponent;

resetForm() {
  this.form.reset();
}

@ViewChildren() 获取多个子元素/指令的 QueryList,支持 .changes 监听动态变化:

@ViewChildren('item') items!: QueryList<ElementRef>;

ngAfterViewInit() {
  this.items.changes.subscribe(list => {
    console.log('Items updated');
  });
}

3. RxJS 响应式编程基础

Observable 与 Observer

// 创建 Observable
const data$ = new Observable(observer => {
  observer.next('Hello');
  setTimeout(() => observer.next('World'), 1000);
  return () => console.log('Unsubscribed');
});

// 订阅 Observable
data$.subscribe({
  next: value => console.log(value),
  error: err => console.error(err),
  complete: () => console.log('Done')
});

常用操作符

操作符用途代码示例
map映射数据this.numbers$.pipe(map(n => n * 2))
filter过滤数据this.values$.pipe(filter(v => v !== null))
switchMap切换到新 Observable,取消旧请求(常用于防抖、HTTP 请求链)this.id$.pipe(switchMap(id => this.userService.getUser(id)))
take只取前 N 个值后自动取消订阅this.data$.pipe(take(1)).subscribe(...)
tap执行副作用(不改变数据流),用于调试、日志、状态更新this.data$.pipe(tap(data => this.log(data)), map(x => x + 1))

Subject 与 BehaviorSubject

  • Subject:多播 Observable,可手动触发。不保留历史值,订阅前的 next 无效。
const subject = new Subject<string>();
subject.subscribe(v => console.log(v));
subject.next('Hello');
  • BehaviorSubject:保留最新值,新订阅者立即收到。必须提供初始值,适合状态管理。
const user$ = new BehaviorSubject<User>(null);
user$.next({ name: 'Alice' });
user$.subscribe(u => console.log(u.name));

异步管道(Async Pipe)

场景代码示例说明
模板显示<p>{{ userName$ | async }}</p>自动处理订阅与取消订阅,避免内存泄漏
属性绑定<user-list [users]="userList$ | async"></user-list>不需要在组件类中手动 subscribe()
与 ngIf 结合<div *ngIf="user$ | async as user">Welcome, {{ user.name }}</div>使用 as 别名避免多次写 async
在 ngFor 中使用<li *ngFor="let item of items$ | async">{{ item }}</li>更简洁,减少组件状态管理
处理 Promise<p>{{ greeting | async }}</p>用法与 Observable 完全一致

三、路由与导航

1. 配置路由表(RouterModule)

Routes 类型定义

import { Routes } from '@angular/router';

const routes: Routes = [
  { path: 'home', component: HomeComponent },
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: '**', component: NotFoundComponent }
];

路径匹配顺序从上到下,必须导入 Routes 类型。

RouterModule.forRoot()

在根模块中注册路由。只在根模块使用 forRoot()

@NgModule({
  imports: [RouterModule.forRoot(appRoutes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

RouterModule.forChild()

在特性模块中注册子路由。特性模块使用 forChild(),避免重复提供服务。

@NgModule({
  imports: [RouterModule.forChild(childRoutes)]
})
export class AdminModule { }

2. 路由跳转

模板中声明式导航,支持字符串或数组形式路径。

<nav>
  <a routerLink="/home">Home</a>
  <a routerLink="/users" [queryParams]="{sort: 'name'}">Users</a>
</nav>

<!-- 数组形式,动态构建路由路径 -->
<a [routerLink]="['/user', userId]">View User</a>

Router.navigate()

编程式导航(TS 中调用),需注入 Router 服务,返回 Promise<boolean>

constructor(private router: Router) {}

goToUser(id: number) {
  this.router.navigate(['/user', id]);
}

Router.navigateByUrl()

按完整 URL 导航,接收绝对路径字符串。

this.router.navigateByUrl('/login?returnUrl=' + current);

3. 路由参数

路径参数

定义动态路由参数:

const routes: Routes = [
  { path: 'user/:id', component: UserDetailComponent }
];

通过 ActivatedRoute 获取参数,使用 + 将字符串转为数字:

constructor(private route: ActivatedRoute) {}

ngOnInit() {
  this.route.params.subscribe(params => {
    this.userId = +params['id']; // + 转数字
  });
}

查询参数

不影响路由匹配,适合筛选、分页:

<a [routerLink]="['/search']" [queryParams]="{q: 'angular'}">Search</a>
this.route.queryParams.subscribe(qp => {
  this.query = qp['q'];
});

编程式传参

this.router.navigate(['/report'], {
  queryParams: { year: 2025, type: 'monthly' }
});

4. 路由守卫

CanActivate

控制是否能进入路由(如权限校验)。返回 booleanObservable<boolean>Promise<boolean>

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(): boolean {
    return this.authService.isLoggedIn();
  }
}

CanDeactivate

控制是否能离开路由(如未保存提示)。常用于表单页面防止误退出。

export interface CanComponentDeactivate {
  canDeactivate: () => boolean;
}

canDeactivate(component: CanComponentDeactivate): boolean {
  return component.canDeactivate();
}

Resolve

在进入路由前预加载数据,避免空白页等待。数据会注入到组件的 data 属性中。

@Injectable()
export class UserResolver implements Resolve<User> {
  resolve(route: ActivatedRouteSnapshot): Observable<User> {
    return this.userService.getUser(+route.paramMap.get('id'));
  }
}

守卫注册

守卫类需在模块或根级提供(providers):

{
  path: 'admin',
  component: AdminComponent,
  canActivate: [AuthGuard],
  resolve: { user: UserResolver }
}

5. 子路由与命名出口

子路由(children)

定义嵌套路由结构,父组件模板中必须有 <router-outlet> 显示子组件。

const routes: Routes = [
  {
    path: 'admin',
    component: AdminComponent,
    children: [
      { path: 'dashboard', component: DashboardComponent },
      { path: 'users', component: UserListComponent }
    ]
  }
];

命名路由出口(named outlets)

用于弹窗、侧边栏等辅助视图,同时显示多个独立路由。

路由配置:

{ path: 'chat', component: ChatComponent, outlet: 'sidebar' }

模板中定义命名出口:

<div class="main">
  <router-outlet></router-outlet>
</div>
<aside>
  <router-outlet name="sidebar"></router-outlet>
</aside>

导航到命名出口,primary 是默认出口名称:

<a [routerLink]="[{ outlets: { sidebar: 'profile' } }]">Open Profile</a>

四、表单处理

1. 模板驱动表单(Template-driven)

基本用法

必须添加 name 属性;需导入 FormsModule

<input [(ngModel)]="user.name" name="name" required />

获取 NgForm 实例,访问表单状态(valid、pristine 等):

<form #f="ngForm" (ngSubmit)="save(f)">
  <input name="email" ngModel required email #email="ngModel" />
  <button [disabled]="!f.valid">Submit</button>
</form>

内置验证指令

基于 HTML5 表单属性,样式类自动添加(如 .ng-invalid):

<input name="email" ngModel required email minlength="5" />

访问控件状态

<input #email="ngModel" name="email" ngModel email />
<span *ngIf="email.invalid && email.touched">Invalid email</span>

2. 响应式表单(Reactive Forms)

FormControl

表示单个表单控件,可设置初始值、同步验证器、异步验证器。

this.nameControl = new FormControl('', [
  Validators.required,
  Validators.minLength(2)
]);

FormGroup

包含多个 FormControl 的表单组。推荐使用 FormBuilder 简化创建。

this.userForm = new FormGroup({
  name: new FormControl('', Validators.required),
  email: new FormControl('', [Validators.required, Validators.email])
});

FormBuilder

需注入 FormBuilder 服务,代码更简洁。

constructor(private fb: FormBuilder) {}

this.userForm = this.fb.group({
  name: ['', Validators.required],
  email: ['', [Validators.required, Validators.email]]
});

FormArray

动态管理多个同类型控件,用于可增删的表单项(如多电话、兴趣)。

this.hobbies = new FormArray([
  new FormControl('')
]);

addHobby() {
  this.hobbies.push(new FormControl(''));
}

3. 表单验证器

内置同步验证器

Validators.compose([
  Validators.required,
  Validators.pattern('[a-zA-Z ]*')
])

自定义同步验证器

适用于 FormGroup 或 FormControl,返回 ValidationErrors | null

static passwordMatch(group: FormGroup): ValidationErrors | null {
  const pwd = group.get('password')?.value;
  const confirm = group.get('confirm')?.value;
  return pwd === confirm ? null : { mismatch: true };
}

异步验证器

异步校验(如查重),返回 Observable<ValidationErrors | null>

checkUsernameUnique(control: FormControl) {
  return timer(500).pipe(
    switchMap(() => this.userService.checkUsername(control.value)),
    map(res => res.available ? null : { taken: true }),
    catchError(() => of(null))
  );
}

设置异步验证器

updateOn: 'blur' 可延迟触发,提升体验。

this.username = new FormControl('', {
  validators: Validators.required,
  asyncValidators: [this.checkUsernameUnique.bind(this)],
  updateOn: 'blur'
});

4. 动态表单构建

动态添加 FormArray 项

配合 *ngFor 渲染动态控件。

addPhone() {
  this.phones.push(this.fb.control(''));
}

动态移除 FormArray 项

确保索引有效,避免越界。

removePhone(i: number) {
  this.phones.removeAt(i);
}

动态启用/禁用控件

适用于查看/编辑模式切换。

toggleEdit() {
  if (this.isEditing) {
    this.userForm.enable();
  } else {
    this.userForm.disable();
  }
}

5. 自定义表单验证器

自定义验证器函数

函数应为静态或独立函数,便于测试和复用。

static ageRange(control: FormControl): ValidationErrors | null {
  const value = control.value;
  if (!value) return null;
  return value >= 18 && value <= 100 ? null : { ageRange: true };
}

在 FormBuilder 中使用

this.age = new FormControl('', [
  Validators.required,
  CustomValidators.ageRange
]);

显示自定义错误消息

错误键名需与返回对象的 key 一致。

<div *ngIf="age.hasError('ageRange')">
  Age must be between 18 and 100.
</div>

五、HTTP 与数据交互

1. HttpClient 模块

导入 HttpClientModule

只需在根模块或共享模块导入一次。

@NgModule({
  imports: [CommonModule, HttpClientModule]
})
export class DataModule { }

注入 HttpClient

必须通过依赖注入获取实例。

export class ApiService {
  constructor(private http: HttpClient) {}

  getData() {
    return this.http.get('/api/data');
  }
}

2. 发起 HTTP 请求

GET 请求

默认返回 Observable;需订阅才能发送请求。

this.http.get<User[]>('/api/users').subscribe(data => {
  this.users = data;
});

POST 请求

第二个参数为请求体(JSON 对象)。

const user = { name: 'John', email: 'john@example.com' };
this.http.post('/api/users', user).subscribe(res => {
  console.log('Created:', res);
});

PUT 请求

替换整个资源;部分更新建议用 PATCH。

this.http.put(`/api/users/${id}`, updatedUser).subscribe(...);

DELETE 请求

通常无响应体,可忽略结果。

this.http.delete(`/api/users/${id}`).subscribe(() => {
  console.log('Deleted');
});

请求选项

支持设置头、查询参数、响应类型等。

const options = {
  headers: new HttpHeaders({ 'Authorization': 'Bearer token' }),
  params: new HttpParams().set('page', '1'),
  responseType: 'json' as const
};
this.http.get('/api/data', options);

3. 请求头与拦截器(HttpInterceptor)

HttpInterceptor 接口

可修改请求头、日志、错误统一处理。

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authReq = req.clone({
      setHeaders: { Authorization: `Bearer ${token}` }
    });
    return next.handle(authReq);
  }
}

注册拦截器

multi: true 表示允许多个拦截器链式执行。

@NgModule({
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
  ]
})

多个拦截器执行顺序

providers 注册顺序执行,前一个 next.handle() 后才进入下一个。

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: LoggerInterceptor, multi: true },
  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]

4. 错误处理(catchError / retry)

catchError 操作符

避免订阅中直接写 error 回调,推荐管道处理。

import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

this.http.get('/api/data').pipe(
  catchError(error => {
    console.error('Request failed:', error);
    return throwError(() => new Error('Data load failed'));
  })
).subscribe(...);

retry 操作符

适用于网络抖动场景;慎用于 POST 等非幂等操作。

this.http.get('/api/data').pipe(
  retry(3),
  catchError(this.handleError)
).subscribe(...);

统一错误处理函数

可结合用户提示(如 Toast)。

private handleError(error: HttpErrorResponse) {
  if (error.status === 401) {
    // 跳转登录
  } else if (error.error instanceof ErrorEvent) {
    console.error('Client error:', error.error.message);
  } else {
    console.error(`Server error ${error.status}:`, error.error);
  }
  return throwError(() => error);
}

5. 上传文件与进度监听

文件上传(FormData)

无需手动设置 Content-Type,浏览器自动设为 multipart/form-data

const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('name', 'My File');

this.http.post('/api/upload', formData).subscribe(...);

监听上传进度

必须使用 http.request() 并设置 reportProgress: trueobserve: 'events'

const req = new HttpRequest('POST', '/api/upload', formData, {
  reportProgress: true,
  observe: 'events'
});

this.http.request(req).subscribe(event => {
  if (event.type === HttpEventType.UploadProgress) {
    const percentDone = event.total
      ? Math.round(100 * event.loaded / event.total)
      : 0;
    this.progress = percentDone;
  } else if (event.type === HttpEventType.Response) {
    console.log('Upload complete!', event.body);
  }
});

HttpEventType 枚举

import { HttpEventType } from '@angular/common/http';

if (event.type === HttpEventType.UploadProgress) { ... }

其他类型:DownloadProgressSent


六、高级特性与优化

1. 依赖注入高级用法

组件级提供者

每个组件实例创建独立服务实例,实现服务隔离。

@Component({
  selector: 'app-user',
  providers: [UserService]
})
export class UserComponent { }

模块级多例

懒加载模块会创建独立实例。

@NgModule({
  providers: [LoggerService] // 非 providedIn: 'root'
})

工厂提供者(useFactory)

支持依赖注入(deps),适合环境配置、条件初始化等场景。

function createConfigService(env: string) {
  return new ConfigService(env === 'prod' ? 'api.prod.com' : 'api.dev.com');
}

@NgModule({
  providers: [
    {
      provide: ConfigService,
      useFactory: createConfigService,
      deps: ['ENVIRONMENT']
    }
  ]
})

@Optional() 可选注入

防止因服务未提供而报错,适用于可插拔功能。

constructor(
  @Optional() private logger: LoggerService
) {
  if (!this.logger) {
    this.logger = new ConsoleLogger(); // 默认实现
  }
}

@Inject() + 默认值

结合 @Optional() 使用,注入特定令牌或默认值。

constructor(
  @Optional() @Inject(LOGGER_TOKEN) private logger: Logger
) {
  this.logger = this.logger || new ConsoleLogger();
}

2. 变更检测机制(Change Detection)

Default vs OnPush 策略

策略行为说明
Default每次事件后检查所有组件安全但性能开销大
OnPush仅当 @Input 引用变化或异步管道时触发提升性能

启用 OnPush:

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserProfileComponent { }

性能优化实践

建议说明
使用 OnPush对纯展示组件启用,减少不必要的脏检查
不可变数据更新返回新对象而非修改原对象:this.items = [...this.items, newItem];
async 管道模板中直接订阅 Observable,Angular 自动管理订阅并触发变更检测
避免模板中调用函数{{ getLabel() }},每次检查都会执行

ChangeDetectorRef 手动控制

constructor(private cdRef: ChangeDetectorRef) {}
方法用途说明
markForCheck()标记组件需检查(配合 OnPush)通知父组件该子组件可能变化
detectChanges()立即执行当前组件及子组件的变更检测谨慎使用,可能破坏变更检测流程
detach() / reattach()暂停/恢复变更检测用于高性能动画或频繁更新场景

3. 管道(Pipe)

内置管道

管道语法用途代码示例
date{{ value | date:'format' }}格式化日期{{ user.birthday | date:'yyyy-MM-dd' }}
uppercase{{ value | uppercase }}转大写{{ name | uppercase }}
lowercase{{ value | lowercase }}转小写{{ title | lowercase }}
titlecase{{ value | titlecase }}首字母大写标题格式{{ 'welcome to angular' | titlecase }}
currency{{ value | currency:'CODE':'display':'format' }}货币显示{{ 99.99 | currency:'USD' }}
percent{{ value | percent:'format' }}百分比格式{{ 0.45 | percent }}
number{{ value | number:'format' }}数字格式化(千分位、小数位){{ 1234.567 | number:'1.2-2' }}
json{{ value | json }}JSON 展示(调试用)<pre>{{ user | json }}</pre>
async{{ observable | async }}自动订阅 Observable/Promise<p>{{ timer$ | async }}</p>

date 管道 支持格式:shortmediumlongfull,或自定义如 MM/dd/yyyy

currency 管道 参数说明:

  • CODE:货币代码(如 USD, EUR, CNY)
  • displaycodesymbolsymbol-narrow
  • format:数字格式(类似 number 管道)

number 管道 格式:{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}

async 管道:对 Observable 自动管理订阅,组件销毁时自动取消;初始值为 null,直到异步数据到达。

自定义纯/非纯管道

纯管道(Pure Pipe):仅输入变化时执行(默认),高性能,不响应对象内部变化。

@Pipe({ name: 'ellipsis' })
export class EllipsisPipe implements PipeTransform {
  transform(value: string, len: number = 10): string {
    return value.length > len ? value.substr(0, len) + '...' : value;
  }
}

非纯管道(Impure Pipe):每次变更检测都执行,谨慎使用,性能开销大。

@Pipe({ name: 'filter', pure: false })
transform(items: any[], keyword: string) {
  return items.filter(i => i.name.includes(keyword));
}

管道链式调用

执行顺序从左到右。

{{ user.name | trim | uppercase | ellipsis:5 }}

4. 内容投影(Content Projection)

基本内容投影

将父组件内容插入子组件模板的 <ng-content> 位置。

<!-- 父组件模板 -->
<card>
  <h2>Title</h2>
  <p>Body</p>
</card>

<!-- Card 组件模板 -->
<div class="card">
  <ng-content></ng-content>
</div>

多槽投影

支持属性选择器 [attr]、类 .class、标签 div

<!-- 模板定义 -->
<ng-content select="[header]"></ng-content>
<ng-content select="[body]"></ng-content>

<!-- 使用 -->
<my-layout>
  <div header>Header</div>
  <div body>Main</div>
</my-layout>

多 ng-content 与默认投影

类似 Web Components 的 slot 机制,每个模板最多一个默认投影。

<header><ng-content select="header" /></header>
<main><ng-content select="main" /></main>
<footer><ng-content select="footer" /></footer>
<ng-content /> <!-- 默认投影,接收未匹配内容 -->

5. 动态组件加载

ViewContainerRef.createComponent

运行时插入组件,需模板占位 <ng-container #dynamic></ng-container>

@ViewChild('dynamic', { read: ViewContainerRef }) container!: ViewContainerRef;

// 动态创建
const componentRef = this.container.createComponent(AlertComponent);
componentRef.instance.message = 'Hello!';

// 销毁
componentRef.destroy();

必须将组件添加到 declarations + 对应模块的 imports,避免内存泄漏。


七、工具链与工程化

1. Angular CLI 工具

命令用途代码示例
ng new创建项目ng new my-app --routing=true --style=scss
ng g生成组件/服务/模块ng g component users/profileng g service authng g module admin --route=admin
ng serve启动开发服务器(热重载,默认 localhost:4200ng serve -o
ng build构建生产资源(输出到 dist/,默认启用 AOT 和压缩)ng build --configuration=production
ng test启动单元测试(Jasmine + Karma)ng test --watch=false --code-coverage
ng e2e运行端到端测试ng e2e

2. 环境配置(environment.ts)

开发环境配置

// src/environments/environment.ts
export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api'
};

生产环境配置

构建时通过 --configuration=production 自动替换。

// src/environments/environment.prod.ts
export const environment = {
  production: true,
  apiUrl: 'https://api.example.com'
};

自定义环境(如 staging)

需在 angular.jsonconfigurations 中添加文件替换映射。

// src/environments/environment.staging.ts
export const environment = {
  production: false,
  apiUrl: 'https://staging-api.example.com'
};

使用环境变量

编译时替换,不支持运行时动态切换。

import { environment } from '../environments/environment';

@Injectable()
export class ApiService {
  private baseUrl = environment.apiUrl;
}

3. 构建优化

优化项说明配置/示例
AOT 编译模板在构建时编译,提升性能,提前发现模板错误生产模式默认启用 "aot": true
懒加载路由模块按需加载,减少初始加载时间loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
Tree-shaking移除未引用代码,减小打包体积构建工具自动完成,依赖 ES6 模块静态分析
生产构建启用压缩、AOT、禁用 DebugModeng build --configuration=production
构建分析使用 source-map-explorer 分析包体积npx source-map-explorer dist/my-app/main.js

4. TypeScript 深度集成

接口(Interface)

推荐用于 API 响应、组件 @Input、服务参数等。

interface User {
  id: number;
  name: string;
  email?: string;
}
users: User[] = [];

类(Class)

支持继承、构造函数、访问修饰符(private、public)。

class BaseModel {
  createdAt: Date = new Date();
}
export class UserService extends BaseService<User> {}

泛型(Generic)

在服务、管道、工具类中广泛使用。

class ApiResponse<T> {
  data: T;
  success: boolean;
}
this.http.get<ApiResponse<User>>('/api/user/1');

装饰器(Decorator)

编译时处理,不影响运行时性能。

@Component({ selector: 'app-user' })
@Input() name: string;
@Injectable()

元数据(Metadata)

Angular 依赖元数据进行依赖注入、变更检测、路由解析等。

类型安全优势

TypeScript 编译阶段即可捕获错误,提供更好的 IDE 智能提示、重构支持和文档化能力。


5. 测试支持

单元测试(Jasmine + Karma)

使用 AAA 模式(Arrange-Act-Assert)。

describe('UserService', () => {
  let service: UserService;

  beforeEach(() => {
    service = new UserService();
  });

  it('should create', () => {
    expect(service).toBeTruthy();
  });
});

TestBed

用于测试组件(含模板)或依赖注入的服务。

beforeEach(async () => {
  await TestBed.configureTestingModule({
    declarations: [UserComponent],
    providers: [UserService]
  }).compileComponents();
});

组件测试

需手动调用 detectChanges() 同步视图。

it('should display user name', () => {
  component.user = { name: 'Alice' };
  fixture.detectChanges();
  const el = fixture.nativeElement;
  expect(el.querySelector('h2').textContent).toContain('Alice');
});

服务测试

服务无模板,测试更简单。

const service = TestBed.inject(AuthService);
expect(service.isLoggedIn()).toBe(false);

管道测试

无需 TestBed(除非依赖注入)。

const pipe = new CapitalizePipe();
expect(pipe.transform('hello')).toBe('Hello');

端到端测试(Cypress)

支持实时重载、时间旅行调试、网络拦截等。

// cypress/e2e/login.spec.ts
describe('Login Flow', () => {
  it('should login successfully', () => {
    cy.visit('/login');
    cy.get('[name="email"]').type('user@test.com');
    cy.get('[name="password"]').type('123456');
    cy.get('button[type="submit"]').click();
    cy.url().should('include', '/dashboard');
  });
});

八、生态与扩展

1. 状态管理方案

NgRx(Redux 模式)

概念用途示例
Action定义状态变更意图createAction('[User] Load User', props<{ id: number }>())
Reducer纯函数,根据 Action 计算新状态,不可修改原 statecreateReducer(initialState, on(loadUser, (state) => ({ ...state, loading: true })))
Store全局状态容器,单一状态树store.select('user')store.dispatch(action)
Effect处理副作用(HTTP 请求等),分发新 ActioncreateEffect(() => this.actions$.pipe(ofType(loadUser), switchMap(...)))

Action 命名规范:[来源] 动作名,如 [User] Load User

替代方案

Akita:基于 Store + Query 模式,更简洁,适合中小型应用,内置缓存、实体管理、查询优化。

@StoreConfig({ name: 'user' })
export class UserStore extends EntityStore<UserState> {
  constructor() { super(); }
}

getUser(id: number) {
  this.http.get<User>(`/api/user/${id}`).subscribe(user =>
    this.store.update({ user, loaded: true })
  );
}

NGXS:类似 Redux,但基于类和装饰器,面向对象风格,减少样板代码。

@State<UserState>({
  name: 'user',
  defaults: { user: null }
})
export class UserState {
  @Action(LoadUser)
  load(ctx: StateContext<UserState>, action: LoadUser) {
    return this.userService.get(action.id).pipe(
      tap(user => ctx.patchState({ user }))
    );
  }
}

选型建议

方案适用场景
NgRx大型复杂应用,团队熟悉 Redux 模式,需严格可预测性
Akita中等复杂度,追求简洁和开发效率
NGXS偏好面向对象风格,减少模板代码
无状态管理简单应用直接使用 Service + BehaviorSubject + async pipe

2. UI 组件库集成

Angular Material

官方 UI 组件库,遵循 Material Design。提供丰富组件,支持主题定制,可访问性良好。

ng add @angular/material
import { MatCardModule, MatButtonModule } from '@angular/material';

@NgModule({
  imports: [MatCardModule, MatButtonModule]
})
<mat-card>
  <mat-card-title>Welcome</mat-card-title>
  <button mat-button>Click</button>
</mat-card>

PrimeNG

功能丰富的第三方组件库,组件数量多,主题丰富(通过 CSS),文档完善。

npm install primeng primeicons
import { PanelModule } from 'primeng/panel';

@NgModule({
  imports: [PanelModule]
})
<p-panel header="Hello">Content</p-panel>

NG-ZORRO(Ant Design of Angular)

阿里开源,Ant Design 风格。国内流行,中文文档完善,支持暗色主题、国际化,适合后台管理系统。

ng add ng-zorro-antd
import { NzButtonModule, NzCardModule } from 'ng-zorro-antd';

@NgModule({
  imports: [NzButtonModule, NzCardModule]
})
<nz-card nzTitle="Welcome">
  <button nz-button>Click Me</button>
</nz-card>

UI 库选型建议

  • 优先考虑设计风格与项目需求匹配
  • 注意包体积,按需导入组件
  • 确保支持响应式布局与可访问性(a11y)

3. 国际化(i18n)与本地化

i18n 属性

<h1 i18n="@@homeTitle">Welcome</h1>
<p i18n="User welcome message@@welcomeMsg">Hello, {{ name }}!</p>
<button i18n>Submit</button>
<!-- 属性翻译 -->
<img [src]="logo" alt="Logo" i18n-alt>

@@id 指定唯一标识,避免文本变更导致翻译失效。

$localize

需在 tsconfig.json 中启用 $localize

const greeting = $localize`Hello, ${name}!`;
alert($localize`:@@confirmDelete:Are you sure?`);

语言包管理

支持多种格式(XLIFF、XMB、JSON)。

# 生成翻译文件
ng extract-i18n --out-file messages.xlf

构建多语言版本

angular.json 中配置每个语言生成独立包,需部署多个版本或结合运行时 i18n(v15+)。

ng build --configuration=fr

i18n 建议

  • 使用 @@id 避免翻译断链
  • 结合 CI/CD 自动提取和集成翻译
  • 考虑运行时动态切换语言(需额外库如 @ngx-translate 或 Angular v15+ 的 Runtime Internationalization)

4. 服务器端渲染(SSR)与 Angular Universal

Angular Universal

官方 SSR 解决方案,服务端运行 Angular 应用,生成 HTML 字符串,客户端”激活”(Hydration)为交互式应用。

ng add @nguniversal/express-engine
# 启动 SSR 服务
npm run dev:ssr

SEO 优化

预渲染完整 HTML,爬虫可直接获取完整页面内容,无需等待 JS 加载。特别适用于营销页、博客、电商列表页。

首屏性能提升

SSR 返回已渲染 HTML,减少白屏时间。需优化服务器响应时间与数据预取。

数据预取(Data Pre-fetching)

在路由 resolver 中调用 API,确保 SSR 时数据就绪,避免客户端重复请求。

平台差异处理

避免在服务端使用 windowdocument 等浏览器 API。

constructor(@Inject(PLATFORM_ID) private platformId: Object) {}

ngOnInit() {
  if (isPlatformBrowser(this.platformId)) {
    // 仅浏览器执行(如 window 操作)
  }
}

SSR 建议

  • 适用于内容型网站(SEO 敏感)
  • 注意服务器资源消耗与部署复杂度
  • 推荐使用托管平台(如 Firebase、Vercel)简化部署