Appearance
Angular 全面指南
Angular是由Google开发和维护的开源Web应用框架,基于TypeScript构建。它是一个完整的平台,提供了构建现代Web应用所需的所有工具和功能。
Angular 简介
核心特性
- TypeScript优先:原生支持TypeScript,提供强类型检查
- 组件化架构:基于组件的开发模式
- 依赖注入:强大的依赖注入系统
- 双向数据绑定:自动同步模型和视图
- 指令系统:扩展HTML功能
- 服务和模块:代码组织和复用
- CLI工具:强大的命令行工具
- 完整生态:路由、HTTP客户端、表单处理等
Angular版本
- AngularJS (1.x):第一代Angular,基于JavaScript
- Angular 2+:完全重写,基于TypeScript
- Angular 15+:最新版本,独立组件、更好的性能
注意:本文主要介绍Angular 2+版本,不是AngularJS
快速开始
环境准备
bash
# 安装Node.js (推荐LTS版本)
# 安装Angular CLI
npm install -g @angular/cli
# 验证安装
ng version创建新项目
bash
# 创建新的Angular项目
ng new my-angular-app
# 选择配置选项
# - 是否添加Angular路由?(Y/n)
# - 选择样式表格式 (CSS/SCSS/Sass/Less)
# 进入项目目录
cd my-angular-app
# 启动开发服务器
ng serve
# 或者指定端口
ng serve --port 4200项目结构
my-angular-app/
├── src/
│ ├── app/
│ │ ├── app.component.ts # 根组件
│ │ ├── app.component.html # 根组件模板
│ │ ├── app.component.css # 根组件样式
│ │ ├── app.component.spec.ts # 根组件测试
│ │ ├── app.module.ts # 根模块
│ │ └── app-routing.module.ts # 路由模块
│ ├── assets/ # 静态资源
│ ├── environments/ # 环境配置
│ ├── index.html # 主HTML文件
│ ├── main.ts # 应用入口
│ └── styles.css # 全局样式
├── angular.json # Angular配置
├── package.json # 依赖配置
└── tsconfig.json # TypeScript配置Angular核心概念
1. 组件 (Components)
基本组件
typescript
// user.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
export interface User {
id: number;
name: string;
email: string;
avatar?: string;
}
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css']
})
export class UserComponent {
@Input() user!: User;
@Input() showActions: boolean = true;
@Output() userClick = new EventEmitter<User>();
@Output() deleteUser = new EventEmitter<number>();
onUserClick(): void {
this.userClick.emit(this.user);
}
onDeleteClick(event: Event): void {
event.stopPropagation();
this.deleteUser.emit(this.user.id);
}
}html
<!-- user.component.html -->
<div class="user-card" (click)="onUserClick()">
<img
[src]="user.avatar || '/assets/default-avatar.png'"
[alt]="user.name"
class="avatar"
>
<div class="user-info">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
<div class="actions" *ngIf="showActions">
<button
class="btn btn-danger"
(click)="onDeleteClick($event)"
>
删除
</button>
</div>
</div>css
/* user.component.css */
.user-card {
display: flex;
align-items: center;
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
margin-bottom: 8px;
cursor: pointer;
transition: background-color 0.2s;
}
.user-card:hover {
background-color: #f5f5f5;
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 16px;
}
.user-info {
flex: 1;
}
.user-info h3 {
margin: 0 0 4px 0;
font-size: 16px;
}
.user-info p {
margin: 0;
color: #666;
font-size: 14px;
}
.actions {
margin-left: 16px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-danger {
background-color: #dc3545;
color: white;
}组件生命周期
typescript
import {
Component,
OnInit,
OnDestroy,
OnChanges,
SimpleChanges,
AfterViewInit,
Input
} from '@angular/core';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-lifecycle-demo',
template: `
<div>
<h2>Lifecycle Demo</h2>
<p>Count: {{ count }}</p>
<p>Input value: {{ inputValue }}</p>
</div>
`
})
export class LifecycleDemoComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit {
@Input() inputValue: string = '';
count: number = 0;
private subscription: Subscription = new Subscription();
private timer: any;
constructor() {
console.log('Constructor called');
}
ngOnChanges(changes: SimpleChanges): void {
console.log('ngOnChanges called', changes);
if (changes['inputValue']) {
console.log('Input value changed:', changes['inputValue'].currentValue);
}
}
ngOnInit(): void {
console.log('ngOnInit called');
// 初始化数据、订阅服务等
this.timer = setInterval(() => {
this.count++;
}, 1000);
}
ngAfterViewInit(): void {
console.log('ngAfterViewInit called');
// 视图初始化完成后的操作
}
ngOnDestroy(): void {
console.log('ngOnDestroy called');
// 清理工作:取消订阅、清除定时器等
this.subscription.unsubscribe();
if (this.timer) {
clearInterval(this.timer);
}
}
}2. 模板语法
数据绑定
html
<!-- 插值表达式 -->
<h1>{{ title }}</h1>
<p>{{ user.name | uppercase }}</p>
<p>{{ 1 + 1 }}</p>
<p>{{ getMessage() }}</p>
<!-- 属性绑定 -->
<img [src]="imageUrl" [alt]="imageAlt">
<button [disabled]="isDisabled">Click me</button>
<div [class.active]="isActive">Active div</div>
<div [style.color]="textColor">Colored text</div>
<!-- 事件绑定 -->
<button (click)="onClick()">Click me</button>
<input (keyup)="onKeyUp($event)" (blur)="onBlur()">
<form (submit)="onSubmit($event)">Submit</form>
<!-- 双向数据绑定 -->
<input [(ngModel)]="username" placeholder="Username">
<p>Hello, {{ username }}!</p>
<!-- 模板引用变量 -->
<input #nameInput type="text">
<button (click)="greet(nameInput.value)">Greet</button>结构指令
html
<!-- *ngIf 条件渲染 -->
<div *ngIf="isLoggedIn; else loginTemplate">
<p>Welcome back!</p>
</div>
<ng-template #loginTemplate>
<p>Please log in</p>
</ng-template>
<!-- *ngFor 列表渲染 -->
<ul>
<li *ngFor="let item of items; let i = index; trackBy: trackByFn">
{{ i + 1 }}. {{ item.name }}
</li>
</ul>
<!-- *ngSwitch 多条件渲染 -->
<div [ngSwitch]="userRole">
<p *ngSwitchCase="'admin'">Admin Panel</p>
<p *ngSwitchCase="'user'">User Dashboard</p>
<p *ngSwitchDefault>Guest View</p>
</div>
<!-- ng-container 分组元素 -->
<ng-container *ngIf="showContent">
<h2>Title</h2>
<p>Content</p>
</ng-container>管道 (Pipes)
html
<!-- 内置管道 -->
<p>{{ message | uppercase }}</p>
<p>{{ message | lowercase }}</p>
<p>{{ price | currency:'USD':'symbol':'1.2-2' }}</p>
<p>{{ today | date:'yyyy-MM-dd' }}</p>
<p>{{ data | json }}</p>
<p>{{ items | slice:0:5 }}</p>
<!-- 链式管道 -->
<p>{{ message | uppercase | slice:0:10 }}</p>
<!-- 自定义管道 -->
<p>{{ text | truncate:50 }}</p>typescript
// 自定义管道
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit: number = 50, trail: string = '...'): string {
if (!value) return '';
return value.length > limit
? value.substring(0, limit) + trail
: value;
}
}3. 服务和依赖注入
创建服务
typescript
// user.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
export interface User {
id: number;
name: string;
email: string;
}
@Injectable({
providedIn: 'root' // 单例服务
})
export class UserService {
private apiUrl = '/api/users';
private usersSubject = new BehaviorSubject<User[]>([]);
public users$ = this.usersSubject.asObservable();
constructor(private http: HttpClient) {
this.loadUsers();
}
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getUserById(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
createUser(user: Omit<User, 'id'>): Observable<User> {
return this.http.post<User>(this.apiUrl, user).pipe(
map(newUser => {
const currentUsers = this.usersSubject.value;
this.usersSubject.next([...currentUsers, newUser]);
return newUser;
})
);
}
updateUser(id: number, user: Partial<User>): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${id}`, user).pipe(
map(updatedUser => {
const currentUsers = this.usersSubject.value;
const index = currentUsers.findIndex(u => u.id === id);
if (index !== -1) {
currentUsers[index] = updatedUser;
this.usersSubject.next([...currentUsers]);
}
return updatedUser;
})
);
}
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`).pipe(
map(() => {
const currentUsers = this.usersSubject.value;
this.usersSubject.next(currentUsers.filter(u => u.id !== id));
})
);
}
private loadUsers(): void {
this.getUsers().subscribe(users => {
this.usersSubject.next(users);
});
}
}在组件中使用服务
typescript
// user-list.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { UserService, User } from '../services/user.service';
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit, OnDestroy {
users: User[] = [];
loading: boolean = false;
error: string | null = null;
private subscription: Subscription = new Subscription();
constructor(private userService: UserService) {}
ngOnInit(): void {
this.loadUsers();
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
loadUsers(): void {
this.loading = true;
this.error = null;
const sub = this.userService.users$.subscribe({
next: (users) => {
this.users = users;
this.loading = false;
},
error: (error) => {
this.error = 'Failed to load users';
this.loading = false;
console.error('Error loading users:', error);
}
});
this.subscription.add(sub);
}
onDeleteUser(userId: number): void {
if (confirm('Are you sure you want to delete this user?')) {
const sub = this.userService.deleteUser(userId).subscribe({
next: () => {
console.log('User deleted successfully');
},
error: (error) => {
this.error = 'Failed to delete user';
console.error('Error deleting user:', error);
}
});
this.subscription.add(sub);
}
}
}4. 模块 (Modules)
根模块
typescript
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { UserListComponent } from './components/user-list/user-list.component';
import { UserComponent } from './components/user/user.component';
import { TruncatePipe } from './pipes/truncate.pipe';
@NgModule({
declarations: [
AppComponent,
UserListComponent,
UserComponent,
TruncatePipe
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }特性模块
typescript
// user.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { UserRoutingModule } from './user-routing.module';
import { UserListComponent } from './components/user-list/user-list.component';
import { UserDetailComponent } from './components/user-detail/user-detail.component';
import { UserFormComponent } from './components/user-form/user-form.component';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [
UserListComponent,
UserDetailComponent,
UserFormComponent
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
UserRoutingModule,
SharedModule
],
exports: [
UserListComponent // 如果需要在其他模块中使用
]
})
export class UserModule { }共享模块
typescript
// shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { LoadingSpinnerComponent } from './components/loading-spinner/loading-spinner.component';
import { ConfirmDialogComponent } from './components/confirm-dialog/confirm-dialog.component';
import { TruncatePipe } from './pipes/truncate.pipe';
import { HighlightDirective } from './directives/highlight.directive';
@NgModule({
declarations: [
LoadingSpinnerComponent,
ConfirmDialogComponent,
TruncatePipe,
HighlightDirective
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule
],
exports: [
// 导出常用模块
CommonModule,
FormsModule,
ReactiveFormsModule,
// 导出共享组件
LoadingSpinnerComponent,
ConfirmDialogComponent,
TruncatePipe,
HighlightDirective
]
})
export class SharedModule { }Angular路由
基本路由配置
typescript
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { AboutComponent } from './components/about/about.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{
path: 'users',
loadChildren: () => import('./modules/user/user.module').then(m => m.UserModule)
},
{ path: '**', component: NotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }嵌套路由和路由参数
typescript
// user-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { UserListComponent } from './components/user-list/user-list.component';
import { UserDetailComponent } from './components/user-detail/user-detail.component';
import { UserFormComponent } from './components/user-form/user-form.component';
import { AuthGuard } from '../core/guards/auth.guard';
const routes: Routes = [
{
path: '',
children: [
{ path: '', component: UserListComponent },
{ path: 'new', component: UserFormComponent, canActivate: [AuthGuard] },
{ path: ':id', component: UserDetailComponent },
{ path: ':id/edit', component: UserFormComponent, canActivate: [AuthGuard] }
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class UserRoutingModule { }路由守卫
typescript
// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
return this.authService.isAuthenticated$.pipe(
map(isAuthenticated => {
if (isAuthenticated) {
return true;
} else {
this.router.navigate(['/login'], {
queryParams: { returnUrl: state.url }
});
return false;
}
})
);
}
}路由使用
typescript
// navigation.component.ts
import { Component } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-navigation',
template: `
<nav>
<a routerLink="/home" routerLinkActive="active">Home</a>
<a routerLink="/about" routerLinkActive="active">About</a>
<a routerLink="/users" routerLinkActive="active">Users</a>
<button (click)="goToUser(123)">Go to User 123</button>
</nav>
<router-outlet></router-outlet>
`,
styles: [`
nav a {
margin-right: 10px;
text-decoration: none;
color: blue;
}
nav a.active {
font-weight: bold;
color: red;
}
`]
})
export class NavigationComponent {
constructor(
private router: Router,
private route: ActivatedRoute
) {}
goToUser(userId: number): void {
this.router.navigate(['/users', userId]);
}
goToUserWithQuery(): void {
this.router.navigate(['/users'], {
queryParams: { page: 1, size: 10 },
fragment: 'top'
});
}
}typescript
// user-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { switchMap } from 'rxjs/operators';
import { UserService, User } from '../../services/user.service';
@Component({
selector: 'app-user-detail',
template: `
<div *ngIf="user">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<button (click)="editUser()">Edit</button>
<button (click)="goBack()">Back</button>
</div>
`
})
export class UserDetailComponent implements OnInit {
user: User | null = null;
constructor(
private route: ActivatedRoute,
private router: Router,
private userService: UserService
) {}
ngOnInit(): void {
// 获取路由参数
this.route.paramMap.pipe(
switchMap(params => {
const id = Number(params.get('id'));
return this.userService.getUserById(id);
})
).subscribe(user => {
this.user = user;
});
// 获取查询参数
this.route.queryParams.subscribe(params => {
console.log('Query params:', params);
});
}
editUser(): void {
if (this.user) {
this.router.navigate(['edit'], { relativeTo: this.route });
}
}
goBack(): void {
this.router.navigate(['../'], { relativeTo: this.route });
}
}表单处理
模板驱动表单
typescript
// template-form.component.ts
import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
@Component({
selector: 'app-template-form',
template: `
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<div>
<label for="name">Name:</label>
<input
type="text"
id="name"
name="name"
[(ngModel)]="user.name"
#name="ngModel"
required
minlength="2"
>
<div *ngIf="name.invalid && name.touched" class="error">
<div *ngIf="name.errors?.['required']">Name is required</div>
<div *ngIf="name.errors?.['minlength']">Name must be at least 2 characters</div>
</div>
</div>
<div>
<label for="email">Email:</label>
<input
type="email"
id="email"
name="email"
[(ngModel)]="user.email"
#email="ngModel"
required
email
>
<div *ngIf="email.invalid && email.touched" class="error">
<div *ngIf="email.errors?.['required']">Email is required</div>
<div *ngIf="email.errors?.['email']">Invalid email format</div>
</div>
</div>
<div>
<label for="age">Age:</label>
<input
type="number"
id="age"
name="age"
[(ngModel)]="user.age"
#age="ngModel"
min="18"
max="100"
>
<div *ngIf="age.invalid && age.touched" class="error">
<div *ngIf="age.errors?.['min']">Age must be at least 18</div>
<div *ngIf="age.errors?.['max']">Age must be less than 100</div>
</div>
</div>
<button type="submit" [disabled]="userForm.invalid">
Submit
</button>
</form>
<div class="debug">
<h3>Form Debug Info:</h3>
<p>Form Valid: {{ userForm.valid }}</p>
<p>Form Value: {{ userForm.value | json }}</p>
</div>
`,
styles: [`
.error {
color: red;
font-size: 12px;
}
.debug {
margin-top: 20px;
padding: 10px;
background-color: #f5f5f5;
}
`]
})
export class TemplateFormComponent {
user = {
name: '',
email: '',
age: null
};
onSubmit(form: NgForm): void {
if (form.valid) {
console.log('Form submitted:', this.user);
// 处理表单提交
}
}
}响应式表单
typescript
// reactive-form.component.ts
import { Component, OnInit } from '@angular/core';
import {
FormBuilder,
FormGroup,
FormArray,
Validators,
AbstractControl
} from '@angular/forms';
@Component({
selector: 'app-reactive-form',
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<div>
<label for="name">Name:</label>
<input type="text" id="name" formControlName="name">
<div *ngIf="name?.invalid && name?.touched" class="error">
<div *ngIf="name?.errors?.['required']">Name is required</div>
<div *ngIf="name?.errors?.['minlength']">Name must be at least 2 characters</div>
</div>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" formControlName="email">
<div *ngIf="email?.invalid && email?.touched" class="error">
<div *ngIf="email?.errors?.['required']">Email is required</div>
<div *ngIf="email?.errors?.['email']">Invalid email format</div>
</div>
</div>
<div formGroupName="address">
<h3>Address</h3>
<div>
<label for="street">Street:</label>
<input type="text" id="street" formControlName="street">
</div>
<div>
<label for="city">City:</label>
<input type="text" id="city" formControlName="city">
</div>
</div>
<div>
<h3>Hobbies</h3>
<div formArrayName="hobbies">
<div *ngFor="let hobby of hobbies.controls; let i = index">
<input type="text" [formControlName]="i">
<button type="button" (click)="removeHobby(i)">Remove</button>
</div>
</div>
<button type="button" (click)="addHobby()">Add Hobby</button>
</div>
<button type="submit" [disabled]="userForm.invalid">
Submit
</button>
</form>
<div class="debug">
<h3>Form Debug Info:</h3>
<p>Form Valid: {{ userForm.valid }}</p>
<p>Form Value: {{ userForm.value | json }}</p>
<p>Form Errors: {{ getFormErrors() | json }}</p>
</div>
`,
styles: [`
.error {
color: red;
font-size: 12px;
}
.debug {
margin-top: 20px;
padding: 10px;
background-color: #f5f5f5;
}
`]
})
export class ReactiveFormComponent implements OnInit {
userForm!: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.userForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(2)]],
email: ['', [Validators.required, Validators.email]],
address: this.fb.group({
street: [''],
city: ['', Validators.required]
}),
hobbies: this.fb.array([
this.fb.control('Reading'),
this.fb.control('Gaming')
])
});
}
// 获取表单控件的便捷方法
get name() { return this.userForm.get('name'); }
get email() { return this.userForm.get('email'); }
get hobbies() { return this.userForm.get('hobbies') as FormArray; }
addHobby(): void {
this.hobbies.push(this.fb.control(''));
}
removeHobby(index: number): void {
this.hobbies.removeAt(index);
}
onSubmit(): void {
if (this.userForm.valid) {
console.log('Form submitted:', this.userForm.value);
// 处理表单提交
} else {
// 标记所有字段为已触摸,显示验证错误
this.markFormGroupTouched(this.userForm);
}
}
private markFormGroupTouched(formGroup: FormGroup): void {
Object.keys(formGroup.controls).forEach(key => {
const control = formGroup.get(key);
control?.markAsTouched();
if (control instanceof FormGroup) {
this.markFormGroupTouched(control);
}
});
}
getFormErrors(): any {
let errors: any = {};
Object.keys(this.userForm.controls).forEach(key => {
const control = this.userForm.get(key);
if (control && !control.valid && control.touched) {
errors[key] = control.errors;
}
});
return errors;
}
}自定义验证器
typescript
// validators.ts
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export class CustomValidators {
// 自定义验证器:禁止特定值
static forbiddenName(forbiddenName: string): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const forbidden = forbiddenName.toLowerCase() === control.value?.toLowerCase();
return forbidden ? { forbiddenName: { value: control.value } } : null;
};
}
// 密码强度验证器
static strongPassword(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (!value) return null;
const hasNumber = /[0-9]/.test(value);
const hasUpper = /[A-Z]/.test(value);
const hasLower = /[a-z]/.test(value);
const hasSpecial = /[#?!@$%^&*-]/.test(value);
const isLengthValid = value.length >= 8;
const passwordValid = hasNumber && hasUpper && hasLower && hasSpecial && isLengthValid;
return passwordValid ? null : {
strongPassword: {
hasNumber,
hasUpper,
hasLower,
hasSpecial,
isLengthValid
}
};
}
// 确认密码验证器
static passwordMatch(passwordField: string, confirmPasswordField: string): ValidatorFn {
return (formGroup: AbstractControl): ValidationErrors | null => {
const password = formGroup.get(passwordField);
const confirmPassword = formGroup.get(confirmPasswordField);
if (!password || !confirmPassword) {
return null;
}
return password.value === confirmPassword.value
? null
: { passwordMismatch: true };
};
}
}
// 使用自定义验证器
this.userForm = this.fb.group({
name: ['', [Validators.required, CustomValidators.forbiddenName('admin')]],
password: ['', [Validators.required, CustomValidators.strongPassword]],
confirmPassword: ['', Validators.required]
}, {
validators: CustomValidators.passwordMatch('password', 'confirmPassword')
});HTTP客户端
基本HTTP操作
typescript
// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, retry, map } from 'rxjs/operators';
export interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
}
@Injectable({
providedIn: 'root'
})
export class ApiService {
private baseUrl = 'https://api.example.com';
constructor(private http: HttpClient) {}
// GET请求
get<T>(endpoint: string, params?: any): Observable<T> {
let httpParams = new HttpParams();
if (params) {
Object.keys(params).forEach(key => {
if (params[key] !== null && params[key] !== undefined) {
httpParams = httpParams.set(key, params[key].toString());
}
});
}
return this.http.get<T>(`${this.baseUrl}/${endpoint}`, {
params: httpParams
}).pipe(
retry(3),
catchError(this.handleError)
);
}
// POST请求
post<T>(endpoint: string, data: any): Observable<T> {
const headers = new HttpHeaders({
'Content-Type': 'application/json'
});
return this.http.post<T>(`${this.baseUrl}/${endpoint}`, data, {
headers
}).pipe(
catchError(this.handleError)
);
}
// PUT请求
put<T>(endpoint: string, data: any): Observable<T> {
return this.http.put<T>(`${this.baseUrl}/${endpoint}`, data).pipe(
catchError(this.handleError)
);
}
// DELETE请求
delete<T>(endpoint: string): Observable<T> {
return this.http.delete<T>(`${this.baseUrl}/${endpoint}`).pipe(
catchError(this.handleError)
);
}
// 文件上传
uploadFile(endpoint: string, file: File): Observable<any> {
const formData = new FormData();
formData.append('file', file);
return this.http.post(`${this.baseUrl}/${endpoint}`, formData, {
reportProgress: true,
observe: 'events'
}).pipe(
catchError(this.handleError)
);
}
// 错误处理
private handleError(error: HttpErrorResponse): Observable<never> {
let errorMessage = 'An unknown error occurred';
if (error.error instanceof ErrorEvent) {
// 客户端错误
errorMessage = `Client Error: ${error.error.message}`;
} else {
// 服务器错误
errorMessage = `Server Error: ${error.status} - ${error.message}`;
}
console.error('HTTP Error:', errorMessage);
return throwError(() => new Error(errorMessage));
}
}HTTP拦截器
typescript
// auth.interceptor.ts
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';
import { AuthService } from '../services/auth.service';
import { Router } from '@angular/router';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(
private authService: AuthService,
private router: Router
) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// 添加认证头
const token = this.authService.getToken();
let authReq = req;
if (token) {
authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
return next.handle(authReq).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
// Token过期,尝试刷新
return this.authService.refreshToken().pipe(
switchMap((newToken: string) => {
// 使用新token重试请求
const newAuthReq = req.clone({
setHeaders: {
Authorization: `Bearer ${newToken}`
}
});
return next.handle(newAuthReq);
}),
catchError(() => {
// 刷新失败,跳转到登录页
this.authService.logout();
this.router.navigate(['/login']);
return throwError(() => error);
})
);
}
return throwError(() => error);
})
);
}
}
// 注册拦截器
// app.module.ts
import { HTTP_INTERCEPTORS } from '@angular/common/http';
@NgModule({
// ...
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
]
})
export class AppModule { }Angular CLI常用命令
项目管理
bash
# 创建新项目
ng new my-app
ng new my-app --routing --style=scss
# 启动开发服务器
ng serve
ng serve --port 4200 --open
# 构建项目
ng build
ng build --prod
ng build --configuration=production
# 运行测试
ng test
ng e2e
# 代码检查
ng lint代码生成
bash
# 生成组件
ng generate component user-list
ng g c user-list --skip-tests
# 生成服务
ng generate service user
ng g s services/user
# 生成模块
ng generate module user --routing
ng g m user --routing
# 生成指令
ng generate directive highlight
ng g d highlight
# 生成管道
ng generate pipe truncate
ng g p truncate
# 生成守卫
ng generate guard auth
ng g g auth
# 生成接口
ng generate interface user
ng g i user
# 生成枚举
ng generate enum user-role
ng g e user-role库管理
bash
# 添加Angular库
ng add @angular/material
ng add @angular/pwa
ng add @ngrx/store
# 更新依赖
ng update
ng update @angular/core @angular/cli
# 分析包大小
ng build --stats-json
npx webpack-bundle-analyzer dist/my-app/stats.jsonAngular最佳实践
1. 项目结构
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
│ │ └── product/
│ ├── layout/ # 布局组件
│ │ ├── header/
│ │ ├── footer/
│ │ └── sidebar/
│ ├── app-routing.module.ts
│ ├── app.component.ts
│ └── app.module.ts
├── assets/ # 静态资源
├── environments/ # 环境配置
└── styles/ # 全局样式
├── _variables.scss
├── _mixins.scss
└── styles.scss2. 组件设计原则
typescript
// 好的组件设计
@Component({
selector: 'app-user-card',
templateUrl: './user-card.component.html',
styleUrls: ['./user-card.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush // 性能优化
})
export class UserCardComponent implements OnInit, OnDestroy {
@Input() user!: User;
@Input() showActions = true;
@Output() userClick = new EventEmitter<User>();
@Output() deleteUser = new EventEmitter<number>();
private destroy$ = new Subject<void>();
constructor(private cdr: ChangeDetectorRef) {}
ngOnInit(): void {
// 初始化逻辑
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
onUserClick(): void {
this.userClick.emit(this.user);
}
onDeleteClick(event: Event): void {
event.stopPropagation();
this.deleteUser.emit(this.user.id);
}
}3. 性能优化
typescript
// 使用OnPush变更检测策略
@Component({
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OptimizedComponent {
@Input() data: any;
constructor(private cdr: ChangeDetectorRef) {}
updateData(): void {
// 手动触发变更检测
this.cdr.markForCheck();
}
}
// 使用TrackBy函数优化ngFor
@Component({
template: `
<div *ngFor="let item of items; trackBy: trackByFn">
{{ item.name }}
</div>
`
})
export class ListComponent {
items: any[] = [];
trackByFn(index: number, item: any): any {
return item.id; // 使用唯一标识符
}
}
// 懒加载模块
const routes: Routes = [
{
path: 'users',
loadChildren: () => import('./user/user.module').then(m => m.UserModule)
}
];4. 状态管理
typescript
// 使用RxJS进行状态管理
@Injectable({
providedIn: 'root'
})
export class StateService {
private stateSubject = new BehaviorSubject(initialState);
public state$ = this.stateSubject.asObservable();
updateState(newState: Partial<State>): void {
const currentState = this.stateSubject.value;
this.stateSubject.next({ ...currentState, ...newState });
}
getState(): State {
return this.stateSubject.value;
}
}Angular生态系统
UI组件库
- Angular Material:官方Material Design组件库
- Ant Design Angular:企业级UI组件库
- PrimeNG:丰富的UI组件集合
- Clarity:VMware开源的设计系统
- Ionic:移动端UI组件库
状态管理
- NgRx:基于Redux的状态管理
- Akita:简单的状态管理解决方案
- NGXS:基于CQRS的状态管理
工具和库
- Angular DevTools:浏览器调试扩展
- Compodoc:文档生成工具
- Angular ESLint:代码质量工具
- Jest:测试框架
- Cypress:端到端测试
总结
Angular是一个功能完整、企业级的前端框架,具有以下特点:
优势
- TypeScript支持:强类型检查,更好的开发体验
- 完整的解决方案:包含路由、HTTP、表单等所有功能
- 企业级特性:依赖注入、模块化、测试支持
- 强大的CLI:代码生成、构建优化
- 长期支持:Google维护,版本稳定
适用场景
- 大型企业应用:复杂的业务逻辑
- 团队协作:统一的开发规范
- 长期维护项目:稳定的架构
- TypeScript项目:原生支持
学习建议
- 掌握TypeScript:Angular的基础
- 理解核心概念:组件、服务、模块、依赖注入
- 学习RxJS:响应式编程
- 掌握Angular CLI:提高开发效率
- 实践项目:构建完整应用
- 学习最佳实践:代码组织、性能优化
Angular适合构建大型、复杂的Web应用,特别是在企业环境中,其完整的工具链和严格的架构能够确保项目的可维护性和可扩展性。
