跳到主要内容
【Angular主要内容归纳】 | 极客日志
<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)
示例:构造函数注入 // 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)
示例:插值绑定 <h1 > {{ title }}</h1 > <p > 当前时间: {{ getCurrentTime() }}</p >
示例:属性绑定 <img[src]="imageUrl"[alt]="imageAlt"><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 =''
<input[(ngModel)]="username"placeholder="用户名"><input[(ngModel)]="email"type="email"placeholder="邮箱"><input[ngModel]="username"(ngModelChange)="username = $event">
7. 指令 (Directives)
示例:结构型指令 <div*ngIf="isLoggedIn">欢迎回来!</div > <div*ngIf="!isLoggedIn">请登录</div > <ul > <li*ngFor="let user of users; let i = index; let first = first"> {{ i + 1 }}. {{ user.name }} <span*ngIf="first">(第一个)</span > </li > </ul > <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 > <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) 组件生命周期钩子允许在组件生命周期的特定时刻执行代码。
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 非常适合构建大型、复杂的企业级应用。
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)
相关免费在线工具 curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online