【Angular主要内容归纳】
Angular 完整教程 - 核心内容详解
目录
- 组件 (Components)
- 模块 (Modules)
- 服务 (Services)
- 依赖注入 (Dependency Injection)
- 路由 (Routing)
- 数据绑定 (Data Binding)
- 指令 (Directives)
- 管道 (Pipes)
- HTTP 客户端
- 表单 (Forms)
- 生命周期钩子 (Lifecycle Hooks)
- RxJS 和 Observable
- 状态管理
- 测试
1. 组件 (Components)
组件是 Angular 应用的基本构建块,每个组件包含:
- 模板 (Template): HTML 视图
- 类 (Class): 组件逻辑和属性
- 样式 (Styles): CSS/SCSS 样式
- 元数据 (Metadata): @Component 装饰器
示例:基础组件
// app.component.tsimport{ Component }from'@angular/core';@Component({ selector:'app-root', templateUrl:'./app.component.html', styleUrls:['./app.component.css']})exportclassAppComponent{ title ='我的 Angular 应用'; count =0;increment(){this.count++;}decrement(){this.count--;}}<!-- app.component.html --><divclass="container"><h1>{{ title }}</h1><p>计数器: {{ count }}</p><button(click)="increment()">增加</button><button(click)="decrement()">减少</button></div>示例:带输入输出的组件
// user-card.component.tsimport{ Component, Input, Output, EventEmitter }from'@angular/core';@Component({ selector:'app-user-card', template:` <div> <h3>{{ user.name }}</h3> <p>邮箱: {{ user.email }}</p> <button (click)="onSelect()">选择用户</button> </div> `})exportclassUserCardComponent{@Input() user!:{ name:string; email:string};@Output() userSelected =newEventEmitter<string>();onSelect(){this.userSelected.emit(this.user.email);}}// parent.component.ts@Component({ selector:'app-parent', template:` <app-user-card [user]="currentUser" (userSelected)="handleUserSelection($event)"> </app-user-card> `})exportclassParentComponent{ currentUser ={ name:'张三', email:'[email protected]'};handleUserSelection(email:string){console.log('选中的用户邮箱:', email);}}2. 模块 (Modules)
模块用于组织应用代码,NgModule 是 Angular 的模块系统。
示例:根模块
// app.module.tsimport{ NgModule }from'@angular/core';import{ BrowserModule }from'@angular/platform-browser';import{ FormsModule }from'@angular/forms';import{ HttpClientModule }from'@angular/common/http';import{ AppComponent }from'./app.component';import{ UserCardComponent }from'./user-card.component';import{ UserService }from'./user.service';@NgModule({ declarations:[ AppComponent, UserCardComponent ], imports:[ BrowserModule, FormsModule, HttpClientModule ], providers:[ UserService ], bootstrap:[AppComponent]})exportclassAppModule{}示例:功能模块
// user.module.tsimport{ NgModule }from'@angular/core';import{ CommonModule }from'@angular/common';import{ UserListComponent }from'./user-list.component';import{ UserDetailComponent }from'./user-detail.component';import{ UserService }from'./user.service';@NgModule({ declarations:[ UserListComponent, UserDetailComponent ], imports:[ CommonModule ], providers:[ UserService ], exports:[ UserListComponent ]})exportclassUserModule{}3. 服务 (Services)
服务用于封装业务逻辑、数据访问和共享功能。
示例:基础服务
// user.service.tsimport{ Injectable }from'@angular/core';import{ HttpClient }from'@angular/common/http';import{ Observable }from'rxjs';exportinterfaceUser{ id:number; name:string; email:string;}@Injectable({ providedIn:'root'// 根级别注入,单例模式})exportclassUserService{private apiUrl ='https://api.example.com/users';constructor(private http: HttpClient){}getUsers(): Observable<User[]>{returnthis.http.get<User[]>(this.apiUrl);}getUserById(id:number): Observable<User>{returnthis.http.get<User>(`${this.apiUrl}/${id}`);}createUser(user: User): Observable<User>{returnthis.http.post<User>(this.apiUrl, user);}updateUser(id:number, user: Partial<User>): Observable<User>{returnthis.http.put<User>(`${this.apiUrl}/${id}`, user);}deleteUser(id:number): Observable<void>{returnthis.http.delete<void>(`${this.apiUrl}/${id}`);}}示例:带缓存的服务
// product.service.tsimport{ Injectable }from'@angular/core';import{ BehaviorSubject, Observable }from'rxjs';import{ tap }from'rxjs/operators';@Injectable({ providedIn:'root'})exportclassProductService{private productsSubject =newBehaviorSubject<any[]>([]);public products$ =this.productsSubject.asObservable();constructor(private http: HttpClient){this.loadProducts();}privateloadProducts(){this.http.get<any[]>('/api/products').pipe(tap(products =>this.productsSubject.next(products))).subscribe();}getProducts(): Observable<any[]>{returnthis.products$;}addProduct(product:any){const current =this.productsSubject.value;this.productsSubject.next([...current, product]);}}4. 依赖注入 (Dependency Injection)
Angular 的依赖注入系统自动管理依赖关系。
示例:构造函数注入
// component.tsimport{ Component }from'@angular/core';import{ UserService }from'./user.service';@Component({ selector:'app-user-list', template:'<div>用户列表</div>'})exportclassUserListComponent{constructor(private userService: UserService){// Angular 自动注入 UserService}ngOnInit(){this.userService.getUsers().subscribe(users =>{console.log(users);});}}示例:可选依赖和注入令牌
// logger.service.tsimport{ Injectable, InjectionToken, Optional, Inject }from'@angular/core';exportconstLOG_LEVEL=newInjectionToken<string>('LOG_LEVEL');@Injectable({ providedIn:'root'})exportclassLoggerService{constructor(@Optional()@Inject(LOG_LEVEL)private logLevel:string='INFO'){console.log('日志级别:',this.logLevel);}log(message:string){console.log(`[${this.logLevel}] ${message}`);}}// app.module.ts providers:[{ provide:LOG_LEVEL, useValue:'DEBUG'}]5. 路由 (Routing)
Angular Router 用于实现单页应用的路由导航。
示例:基础路由配置
// app-routing.module.tsimport{ NgModule }from'@angular/core';import{ RouterModule, Routes }from'@angular/router';import{ HomeComponent }from'./home/home.component';import{ UserListComponent }from'./user/user-list.component';import{ UserDetailComponent }from'./user/user-detail.component';import{ NotFoundComponent }from'./not-found/not-found.component';const routes: Routes =[{ path:'', redirectTo:'/home', pathMatch:'full'},{ path:'home', component: HomeComponent },{ path:'users', component: UserListComponent },{ path:'users/:id', component: UserDetailComponent },{ path:'**', component: NotFoundComponent }];@NgModule({ imports:[RouterModule.forRoot(routes)], exports:[RouterModule]})exportclassAppRoutingModule{}示例:路由守卫
// auth.guard.tsimport{ Injectable }from'@angular/core';import{ CanActivate, Router }from'@angular/router';import{ AuthService }from'./auth.service';@Injectable({ providedIn:'root'})exportclassAuthGuardimplementsCanActivate{constructor(private authService: AuthService,private router: Router ){}canActivate():boolean{if(this.authService.isAuthenticated()){returntrue;}else{this.router.navigate(['/login']);returnfalse;}}}// 在路由中使用守卫const routes: Routes =[{ path:'dashboard', component: DashboardComponent, canActivate:[AuthGuard]}];示例:路由参数和查询参数
// user-detail.component.tsimport{ Component, OnInit }from'@angular/core';import{ ActivatedRoute, Router }from'@angular/router';@Component({ selector:'app-user-detail', template:` <div> <h2>用户详情</h2> <p>ID: {{ userId }}</p> <p>模式: {{ mode }}</p> <button (click)="goBack()">返回</button> </div> `})exportclassUserDetailComponentimplementsOnInit{ userId!:number; mode!:string;constructor(private route: ActivatedRoute,private router: Router ){}ngOnInit(){// 获取路由参数this.userId =+this.route.snapshot.paramMap.get('id')!;// 获取查询参数this.mode =this.route.snapshot.queryParamMap.get('mode')||'view';// 监听参数变化this.route.paramMap.subscribe(params =>{this.userId =+params.get('id')!;});}goBack(){this.router.navigate(['/users']);}}6. 数据绑定 (Data Binding)
Angular 支持多种数据绑定方式。
示例:插值绑定
<!-- 显示组件属性 --><h1>{{ title }}</h1><p>当前时间: {{ getCurrentTime() }}</p>示例:属性绑定
<!-- 绑定 HTML 属性 --><img[src]="imageUrl"[alt]="imageAlt"><!-- 绑定 DOM 属性 --><button[disabled]="isDisabled">提交</button><!-- 绑定类 --><div[class.active]="isActive"[class.error]="hasError"></div><!-- 绑定样式 --><div[style.color]="textColor"[style.font-size.px]="fontSize"></div>示例:事件绑定
<!-- 点击事件 --><button(click)="handleClick()">点击</button><!-- 带事件对象 --><button(click)="handleClick($event)">点击</button><!-- 输入事件 --><input(input)="onInput($event)"(keyup.enter)="onEnter()">示例:双向绑定
// component.tsexportclassFormComponent{ username =''; email ='';}<!-- 使用 ngModel 双向绑定 --><input[(ngModel)]="username"placeholder="用户名"><input[(ngModel)]="email"type="email"placeholder="邮箱"><!-- 等价写法 --><input[ngModel]="username"(ngModelChange)="username = $event">7. 指令 (Directives)
指令用于扩展 HTML 元素的功能。
示例:结构型指令
<!-- *ngIf - 条件渲染 --><div*ngIf="isLoggedIn">欢迎回来!</div><div*ngIf="!isLoggedIn">请登录</div><!-- *ngFor - 列表渲染 --><ul><li*ngFor="let user of users; let i = index; let first = first"> {{ i + 1 }}. {{ user.name }} <span*ngIf="first">(第一个)</span></li></ul><!-- *ngSwitch - 多条件判断 --><div[ngSwitch]="status"><p*ngSwitchCase="'loading'">加载中...</p><p*ngSwitchCase="'success'">成功!</p><p*ngSwitchCase="'error'">错误!</p><p*ngSwitchDefault>未知状态</p></div>示例:属性型指令
// highlight.directive.tsimport{ Directive, ElementRef, Input, Renderer2, OnInit }from'@angular/core';@Directive({ selector:'[appHighlight]'})exportclassHighlightDirectiveimplementsOnInit{@Input() appHighlight ='yellow';@Input() defaultColor ='transparent';constructor(private el: ElementRef,private renderer: Renderer2 ){}ngOnInit(){this.setBackgroundColor(this.appHighlight ||this.defaultColor);}privatesetBackgroundColor(color:string){this.renderer.setStyle(this.el.nativeElement,'background-color', color);}}<!-- 使用自定义指令 --><pappHighlight="yellow">高亮文本</p><p[appHighlight]="highlightColor">动态高亮</p>示例:结构型指令(自定义)
// unless.directive.tsimport{ Directive, Input, TemplateRef, ViewContainerRef }from'@angular/core';@Directive({ selector:'[appUnless]'})exportclassUnlessDirective{private hasView =false;constructor(private templateRef: TemplateRef<any>,private viewContainer: ViewContainerRef ){}@Input()setappUnless(condition:boolean){if(!condition &&!this.hasView){this.viewContainer.createEmbeddedView(this.templateRef);this.hasView =true;}elseif(condition &&this.hasView){this.viewContainer.clear();this.hasView =false;}}}<!-- 使用自定义结构型指令 --><div*appUnless="isHidden">这段内容在 isHidden 为 false 时显示</div>8. 管道 (Pipes)
管道用于转换数据显示格式。
示例:内置管道
<!-- 日期管道 --><p>{{ today | date:'yyyy-MM-dd' }}</p><p>{{ today | date:'full' }}</p><!-- 货币管道 --><p>{{ price | currency:'CNY':'symbol':'1.2-2' }}</p><!-- 数字管道 --><p>{{ number | number:'1.2-2' }}</p><!-- 百分比管道 --><p>{{ ratio | percent:'1.2-2' }}</p><!-- 大写/小写管道 --><p>{{ text | uppercase }}</p><p>{{ text | lowercase }}</p><!-- 切片管道 --><p>{{ longText | slice:0:100 }}...</p><!-- JSON 管道 --><pre>{{ object | json }}</pre>示例:自定义管道
// truncate.pipe.tsimport{ Pipe, PipeTransform }from'@angular/core';@Pipe({ name:'truncate'})exportclassTruncatePipeimplementsPipeTransform{transform(value:string, limit:number=20, trail:string='...'):string{if(!value)return'';return value.length > limit ? value.substring(0, limit)+ trail : value;}}// filter.pipe.tsimport{ Pipe, PipeTransform }from'@angular/core';@Pipe({ name:'filter', pure:false// 非纯管道,每次变更检测都会执行})exportclassFilterPipeimplementsPipeTransform{transform(items:any[], searchText:string):any[]{if(!items)return[];if(!searchText)return items; searchText = searchText.toLowerCase();return items.filter(item => item.name.toLowerCase().includes(searchText));}}<!-- 使用自定义管道 --><p>{{ longText | truncate:50 }}</p><div*ngFor="let item of items | filter:searchText"> {{ item.name }} </div>9. HTTP 客户端
Angular HttpClient 用于发送 HTTP 请求。
示例:GET 请求
// user.service.tsimport{ HttpClient, HttpParams }from'@angular/common/http';import{ Observable }from'rxjs';@Injectable({ providedIn:'root'})exportclassUserService{private apiUrl ='https://api.example.com/users';constructor(private http: HttpClient){}// 简单 GET 请求getUsers(): Observable<User[]>{returnthis.http.get<User[]>(this.apiUrl);}// 带查询参数的 GET 请求searchUsers(keyword:string, page:number=1): Observable<User[]>{const params =newHttpParams().set('keyword', keyword).set('page', page.toString());returnthis.http.get<User[]>(this.apiUrl,{ params });}// 带响应类型的 GET 请求downloadFile(): Observable<Blob>{returnthis.http.get(this.apiUrl +'/export',{ responseType:'blob'});}}示例:POST/PUT/DELETE 请求
// user.service.tscreateUser(user: User): Observable<User>{returnthis.http.post<User>(this.apiUrl, user);}updateUser(id:number, user: Partial<User>): Observable<User>{returnthis.http.put<User>(`${this.apiUrl}/${id}`, user);}deleteUser(id:number): Observable<void>{returnthis.http.delete<void>(`${this.apiUrl}/${id}`);}示例:HTTP 拦截器
// auth.interceptor.tsimport{ Injectable }from'@angular/core';import{ HttpRequest, HttpHandler, HttpEvent, HttpInterceptor }from'@angular/common/http';import{ Observable }from'rxjs';@Injectable()exportclassAuthInterceptorimplementsHttpInterceptor{intercept( request: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>>{// 添加认证 tokenconst token = localStorage.getItem('token');if(token){ request = request.clone({ setHeaders:{ Authorization:`Bearer ${token}`}});}return next.handle(request);}}// app.module.tsimport{HTTP_INTERCEPTORS}from'@angular/common/http'; providers:[{ provide:HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi:true}]示例:错误处理
// user.service.tsimport{ catchError, retry }from'rxjs/operators';import{ throwError }from'rxjs';getUsers(): Observable<User[]>{returnthis.http.get<User[]>(this.apiUrl).pipe(retry(3),// 重试 3 次catchError(this.handleError));}privatehandleError(error:any){let errorMessage ='发生未知错误';if(error.error instanceofErrorEvent){ errorMessage =`错误: ${error.error.message}`;}else{ errorMessage =`错误代码: ${error.status}\n消息: ${error.message}`;}console.error(errorMessage);returnthrowError(()=> errorMessage);}10. 表单 (Forms)
Angular 提供两种表单方式:模板驱动表单和响应式表单。
示例:模板驱动表单
// login.component.tsimport{ Component }from'@angular/core';@Component({ selector:'app-login', template:` <form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)"> <div> <label>用户名:</label> <input name="username" ngModel required minlength="3" #username="ngModel"> <div *ngIf="username.invalid && username.touched"> <span *ngIf="username.errors?.['required']">用户名必填</span> <span *ngIf="username.errors?.['minlength']">至少3个字符</span> </div> </div> <div> <label>密码:</label> <input type="password" name="password" ngModel required> </div> <button type="submit" [disabled]="loginForm.invalid"> 登录 </button> </form> `})exportclassLoginComponent{onSubmit(form:any){if(form.valid){console.log('表单数据:', form.value);}}}示例:响应式表单
// register.component.tsimport{ Component, OnInit }from'@angular/core';import{ FormBuilder, FormGroup, Validators, AbstractControl }from'@angular/forms';@Component({ selector:'app-register', template:` <form [formGroup]="registerForm" (ngSubmit)="onSubmit()"> <div> <label>邮箱:</label> <input formControlName="email"> <div *ngIf="email.invalid && email.touched"> <span *ngIf="email.errors?.['required']">邮箱必填</span> <span *ngIf="email.errors?.['email']">邮箱格式不正确</span> </div> </div> <div> <label>密码:</label> <input type="password" formControlName="password"> </div> <div> <label>确认密码:</label> <input type="password" formControlName="confirmPassword"> <div *ngIf="registerForm.errors?.['passwordMismatch']"> 密码不匹配 </div> </div> <button type="submit" [disabled]="registerForm.invalid"> 注册 </button> </form> `})exportclassRegisterComponentimplementsOnInit{ registerForm!: FormGroup;constructor(private fb: FormBuilder){}ngOnInit(){this.registerForm =this.fb.group({ email:['',[Validators.required, Validators.email]], password:['',[Validators.required, Validators.minLength(6)]], confirmPassword:['']},{ validators:this.passwordMatchValidator });}getemail(){returnthis.registerForm.get('email')!;}getpassword(){returnthis.registerForm.get('password')!;}getconfirmPassword(){returnthis.registerForm.get('confirmPassword')!;}passwordMatchValidator(control: AbstractControl){const password = control.get('password');const confirmPassword = control.get('confirmPassword');if(password && confirmPassword && password.value !== confirmPassword.value){return{ passwordMismatch:true};}returnnull;}onSubmit(){if(this.registerForm.valid){console.log('表单数据:',this.registerForm.value);}}}示例:动态表单
// dynamic-form.component.tsimport{ Component }from'@angular/core';import{ FormArray, FormBuilder, FormGroup, Validators }from'@angular/forms';@Component({ selector:'app-dynamic-form', template:` <form [formGroup]="form"> <div formArrayName="hobbies"> <div *ngFor="let hobby of hobbies.controls; let i = index" [formGroupName]="i"> <input formControlName="name" placeholder="爱好名称"> <button type="button" (click)="removeHobby(i)">删除</button> </div> </div> <button type="button" (click)="addHobby()">添加爱好</button> </form> `})exportclassDynamicFormComponent{ form!: FormGroup;constructor(private fb: FormBuilder){this.form =this.fb.group({ hobbies:this.fb.array([])});}gethobbies(){returnthis.form.get('hobbies')as FormArray;}addHobby(){const hobbyGroup =this.fb.group({ name:['', Validators.required]});this.hobbies.push(hobbyGroup);}removeHobby(index:number){this.hobbies.removeAt(index);}}11. 生命周期钩子 (Lifecycle Hooks)
组件生命周期钩子允许在组件生命周期的特定时刻执行代码。
Angular 组件生命周期(常用顺序与作用):
- constructor
注入依赖,尽量不做复杂逻辑,不访问输入属性。 - ngOnChanges(changes: SimpleChanges)
输入属性 @Input 变更时触发;首次也会触发。可根据 changes 做差异化处理。 - ngOnInit
初始化时机,适合发起首次请求、初始化数据;此时可安全读取 @Input。 - ngDoCheck
自定义变更检测钩子,谨慎使用,避免重计算导致性能问题。 - ngAfterContentInit / ngAfterContentChecked
针对内容投影()的初始化/变更检查。 - ngAfterViewInit / ngAfterViewChecked
视图(模板、子组件)初始化/变更后触发;此时可安全访问 @ViewChild/@ViewChildren。 - ngOnDestroy
销毁前清理:取消订阅、清除计时器/监听、销毁资源。
常用实践:
数据初始化:放 ngOnInit。
监听输入变化:用 ngOnChanges 或 @Input() set …。
访问子组件/DOM:放 ngAfterViewInit。
取消订阅:在 ngOnDestroy 里统一清理(如 takeUntil/Subscription.unsubscribe)。
示例:完整生命周期
// lifecycle.component.tsimport{ Component, OnInit, OnChanges, OnDestroy, AfterViewInit, AfterViewChecked, AfterContentInit, AfterContentChecked, DoCheck, Input, SimpleChanges }from'@angular/core';@Component({ selector:'app-lifecycle', template:'<div>{{ message }}</div>'})exportclassLifecycleComponentimplementsOnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy {@Input() message ='';constructor(){console.log('1. constructor');}ngOnChanges(changes: SimpleChanges){console.log('2. ngOnChanges', changes);}ngOnInit(){console.log('3. ngOnInit');}ngDoCheck(){console.log('4. ngDoCheck');}ngAfterContentInit(){console.log('5. ngAfterContentInit');}ngAfterContentChecked(){console.log('6. ngAfterContentChecked');}ngAfterViewInit(){console.log('7. ngAfterViewInit');}ngAfterViewChecked(){console.log('8. ngAfterViewChecked');}ngOnDestroy(){console.log('9. ngOnDestroy - 清理资源');}}示例:使用 ViewChild 和 AfterViewInit
// component.tsimport{ Component, ViewChild, AfterViewInit, ElementRef }from'@angular/core';@Component({ selector:'app-example', template:` <div #myDiv>内容</div> <app-child #child></app-child> `})exportclassExampleComponentimplementsAfterViewInit{@ViewChild('myDiv') myDiv!: ElementRef;@ViewChild('child') childComponent!: ChildComponent;ngAfterViewInit(){// 此时可以安全访问子组件和 DOM 元素console.log(this.myDiv.nativeElement.textContent);this.childComponent.doSomething();}}12. RxJS 和 Observable
RxJS 是 Angular 中处理异步操作的核心库。
示例:基础 Observable
import{ Observable,of, from }from'rxjs';// 创建简单的 Observableconst simple$ =of(1,2,3); simple$.subscribe(value =>console.log(value));// 从数组创建const array$ =from([1,2,3]); array$.subscribe(value =>console.log(value));// 从 Promise 创建const promise$ =from(fetch('/api/data')); promise$.subscribe(response =>console.log(response));示例:常用操作符
import{ map, filter, tap, catchError, switchMap, debounceTime }from'rxjs/operators';// map - 转换数据this.http.get<User[]>('/api/users').pipe(map(users => users.map(user => user.name))).subscribe(names =>console.log(names));// filter - 过滤数据this.http.get<User[]>('/api/users').pipe(filter(users => users.length >0)).subscribe(users =>console.log(users));// tap - 执行副作用操作this.http.get<User[]>('/api/users').pipe(tap(users =>console.log('获取到用户:', users))).subscribe();// switchMap - 切换 Observablethis.searchControl.valueChanges.pipe(debounceTime(300),switchMap(keyword =>this.searchUsers(keyword))).subscribe(results =>console.log(results));// catchError - 错误处理this.http.get<User[]>('/api/users').pipe(catchError(error =>{console.error('错误:', error);returnof([]);// 返回默认值})).subscribe();示例:Subject 和 BehaviorSubject
import{ Subject, BehaviorSubject }from'rxjs';// Subject - 多播 ObservableexportclassMessageService{private messageSubject =newSubject<string>();public messages$ =this.messageSubject.asObservable();sendMessage(message:string){this.messageSubject.next(message);}}// BehaviorSubject - 带初始值的 SubjectexportclassStateService{private stateSubject =newBehaviorSubject<number>(0);public state$ =this.stateSubject.asObservable();updateState(value:number){this.stateSubject.next(value);}getCurrentState():number{returnthis.stateSubject.value;}}示例:取消订阅
import{ Component, OnDestroy }from'@angular/core';import{ Subscription }from'rxjs';@Component({ selector:'app-example', template:'<div>{{ data }}</div>'})exportclassExampleComponentimplementsOnDestroy{ data:any;private subscription =newSubscription();ngOnInit(){// 方式1: 使用 Subscriptionconst sub1 =this.service.getData().subscribe(data =>{this.data = data;});this.subscription.add(sub1);// 方式2: 使用 takeUntilthis.service.getData().pipe(takeUntil(this.destroy$)).subscribe(data =>{this.data = data;});}ngOnDestroy(){this.subscription.unsubscribe();}}13. 状态管理
示例:使用 Service 进行状态管理
// cart.service.tsimport{ Injectable }from'@angular/core';import{ BehaviorSubject, Observable }from'rxjs';exportinterfaceCartItem{ id:number; name:string; price:number; quantity:number;}@Injectable({ providedIn:'root'})exportclassCartService{private cartSubject =newBehaviorSubject<CartItem[]>([]);public cart$ =this.cartSubject.asObservable();getCart(): Observable<CartItem[]>{returnthis.cart$;}addItem(item: CartItem){const current =this.cartSubject.value;const existing = current.find(i => i.id === item.id);if(existing){ existing.quantity += item.quantity;}else{ current.push(item);}this.cartSubject.next([...current]);}removeItem(id:number){const current =this.cartSubject.value.filter(item => item.id !== id);this.cartSubject.next(current);}clearCart(){this.cartSubject.next([]);}getTotal():number{returnthis.cartSubject.value.reduce((sum, item)=> sum + item.price * item.quantity,0);}}示例:使用 NgRx(状态管理库)
// cart.actions.tsimport{ createAction, props }from'@ngrx/store';import{ CartItem }from'./cart.model';exportconst addItem =createAction('[Cart] Add Item',props<{ item: CartItem }>());exportconst removeItem =createAction('[Cart] Remove Item',props<{ id:number}>());exportconst clearCart =createAction('[Cart] Clear');// cart.reducer.tsimport{ createReducer, on }from'@ngrx/store';import{ addItem, removeItem, clearCart }from'./cart.actions';import{ CartItem }from'./cart.model';exportinterfaceCartState{ items: CartItem[];}exportconst initialState: CartState ={ items:[]};exportconst cartReducer =createReducer( initialState,on(addItem,(state,{ item })=>{const existing = state.items.find(i => i.id === item.id);if(existing){return{...state, items: state.items.map(i => i.id === item.id ?{...i, quantity: i.quantity + item.quantity }: i )};}return{...state, items:[...state.items, item]};}),on(removeItem,(state,{ id })=>({...state, items: state.items.filter(item => item.id !== id)})),on(clearCart, state =>({...state, items:[]})));// component.tsimport{ Component }from'@angular/core';import{ Store }from'@ngrx/store';import{ Observable }from'rxjs';import{ addItem }from'./cart.actions';import{ CartState }from'./cart.reducer';@Component({ selector:'app-cart', template:` <div *ngFor="let item of items$ | async"> {{ item.name }} - {{ item.price }} </div> `})exportclassCartComponent{ items$: Observable<CartItem[]>;constructor(private store: Store<{ cart: CartState }>){this.items$ =this.store.select(state => state.cart.items);}addToCart(item: CartItem){this.store.dispatch(addItem({ item }));}}14. 测试
示例:组件测试
// app.component.spec.tsimport{ TestBed }from'@angular/core/testing';import{ AppComponent }from'./app.component';describe('AppComponent',()=>{beforeEach(async()=>{await TestBed.configureTestingModule({ declarations:[AppComponent]}).compileComponents();});it('should create the app',()=>{const fixture = TestBed.createComponent(AppComponent);const app = fixture.componentInstance;expect(app).toBeTruthy();});it('should increment count',()=>{const fixture = TestBed.createComponent(AppComponent);const component = fixture.componentInstance;const initialCount = component.count; component.increment();expect(component.count).toBe(initialCount +1);});it('should render title',()=>{const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges();const compiled = fixture.nativeElement;expect(compiled.querySelector('h1').textContent).toContain('我的 Angular 应用');});});示例:服务测试
// user.service.spec.tsimport{ TestBed }from'@angular/core/testing';import{ HttpClientTestingModule, HttpTestingController }from'@angular/common/http/testing';import{ UserService }from'./user.service';describe('UserService',()=>{let service: UserService;let httpMock: HttpTestingController;beforeEach(()=>{ TestBed.configureTestingModule({ imports:[HttpClientTestingModule], providers:[UserService]}); service = TestBed.inject(UserService); httpMock = TestBed.inject(HttpTestingController);});afterEach(()=>{ httpMock.verify();});it('should fetch users',()=>{const mockUsers =[{ id:1, name:'张三', email:'[email protected]'},{ id:2, name:'李四', email:'[email protected]'}]; service.getUsers().subscribe(users =>{expect(users.length).toBe(2);expect(users).toEqual(mockUsers);});const req = httpMock.expectOne('/api/users');expect(req.request.method).toBe('GET'); req.flush(mockUsers);});});总结
Angular 是一个功能强大的前端框架,主要特点包括:
- 组件化架构 - 可复用的组件系统
- 依赖注入 - 自动管理依赖关系
- 模块化 - 组织代码结构
- 路由系统 - 单页应用导航
- 响应式编程 - RxJS 处理异步操作
- 双向数据绑定 - 简化数据流
- 指令系统 - 扩展 HTML 功能
- 表单处理 - 强大的表单验证
- HTTP 客户端 - 处理 API 请求
- 测试支持 - 完整的测试工具
这些特性使得 Angular 非常适合构建大型、复杂的企业级应用。
下边是Angular项目结构示例
my-angular-app/
├─ angular.json # Angular CLI 配置
├─ package.json # 项目依赖与脚本
├─ tsconfig.json # TypeScript 编译配置
├─ tsconfig.app.json # 应用 TS 配置
├─ tsconfig.spec.json # 测试 TS 配置
├─ karma.conf.js # 单元测试(Karma)配置
├─ src/
│ ├─ main.ts # 应用入口(bootstrap AppModule)
│ ├─ index.html # 单页应用入口 HTML
│ ├─ styles.scss # 全局样式
│ ├─ polyfills.ts # 兼容性填充
│ ├─ environments/ # 环境变量(prod/dev)
│ │ ├─ environment.ts
│ │ └─ environment.prod.ts
│ ├─ app/
│ │ ├─ app.module.ts # 根模块
│ │ ├─ app.component.* # 根组件
│ │ ├─ core/ # 核心服务、拦截器、守卫(全局单例)
│ │ ├─ shared/ # 可复用组件、指令、管道、工具
│ │ ├─ features/ # 业务功能模块(按域拆分)
│ │ │ ├─ home/ # 示例功能模块
│ │ │ ├─ users/ # 示例功能模块
│ │ │ └─ … # 其它业务域
│ │ ├─ services/ #(可选)跨功能通用服务
│ │ ├─ models/ #(可选)接口/类型定义
│ │ ├─ guards/ #(可选)路由守卫
│ │ ├─ interceptors/ #(可选)HTTP 拦截器
│ │ ├─ store/ #(可选)状态管理(NgRx、Akita 等)
│ │ └─ app-routing.module.ts # 根路由
│ └─ assets/ # 静态资源(图片、字体、i18n 等)
└─ e2e/ # 端到端测试(如使用 Cypress/Protractor)