一、核心基础概念
1. 组件(Component)
@Component 装饰器
用于定义 Angular 组件及其元数据。
selector 必须唯一;templateUrl 和 template 二选一;组件类必须用 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>
安全导航操作符 ?.
防止访问 null 或 undefined 属性时报错。用于可能为空的对象链,避免模板崩溃。
<p>{{ user?.profile?.email }}</p>
非空断言操作符 !
告诉 TypeScript 编译器该值不为 null 或 undefined。仅在确定不为空时使用,否则运行时报错。
@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. 路由跳转
routerLink 指令
模板中声明式导航,支持字符串或数组形式路径。
<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
控制是否能进入路由(如权限校验)。返回 boolean、Observable<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: true 和 observe: '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) { ... }
其他类型:DownloadProgress、Sent。
六、高级特性与优化
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 管道 支持格式:short、medium、long、full,或自定义如 MM/dd/yyyy。
currency 管道 参数说明:
CODE:货币代码(如 USD, EUR, CNY)display:code、symbol、symbol-narrowformat:数字格式(类似 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/profile、ng g service auth、ng g module admin --route=admin |
ng serve | 启动开发服务器(热重载,默认 localhost:4200) | ng 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.json 的 configurations 中添加文件替换映射。
// 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、禁用 DebugMode | ng 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 计算新状态,不可修改原 state | createReducer(initialState, on(loadUser, (state) => ({ ...state, loading: true }))) |
| Store | 全局状态容器,单一状态树 | store.select('user')、store.dispatch(action) |
| Effect | 处理副作用(HTTP 请求等),分发新 Action | createEffect(() => 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 时数据就绪,避免客户端重复请求。
平台差异处理
避免在服务端使用 window、document 等浏览器 API。
constructor(@Inject(PLATFORM_ID) private platformId: Object) {}
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
// 仅浏览器执行(如 window 操作)
}
}
SSR 建议:
- 适用于内容型网站(SEO 敏感)
- 注意服务器资源消耗与部署复杂度
- 推荐使用托管平台(如 Firebase、Vercel)简化部署