Angular基础教程
Angular是由Google维护的一个开源前端框架,它提供了完整的解决方案用于构建复杂的企业级单页面应用程序(SPA)。Angular采用TypeScript语言开发,提供了组件化架构、依赖注入、响应式编程和全面的工具生态系统。
核心价值
Angular框架优势
- 🏗️ 完整框架:提供一站式解决方案,不需要选择额外库
- 📦 组件化架构:基于组件的模块化开发方式
- 🧰 依赖注入:强大的依赖注入系统简化测试和管理
- 📋 TypeScript支持:内置TypeScript支持,提供类型安全
- 🛠️ 工具生态:Angular CLI提供完善的开发工具链
- 🔄 双向绑定:灵活的数据绑定机制
1. Angular架构概览
Angular应用是由模块、组件和服务等构建块组成的:
1.1 核心构建块
- 模块(NgModule): 相关组件、指令、管道和服务的容器
- 组件(Component): 控制视图的类,负责数据和UI交互
- 模板(Template): 定义组件视图的HTML
- 元数据(Metadata): 通过装饰器定义类的行为
- 服务(Service): 共享数据和功能的可复用类
- 依赖注入(DI): 管理组件和服务依赖关系的设计模式
2. 开始使用Angular
2.1 安装Angular CLI
Angular CLI是一个命令行工具,用于初始化、开发、构建和维护Angular应用:
bash
1# 安装Angular CLI2npm install -g @angular/cli34# 查看版本5ng version2.2 创建新项目
使用Angular CLI创建一个新项目:
bash
1# 创建新项目2ng new my-angular-app34# 选项说明:5# --strict: 启用严格模式6# --routing: 添加路由模块7# --style=scss: 使用SCSS预处理器89cd my-angular-app10ng serve2.3 Angular项目结构
1my-angular-app/2├── src/ # 源代码目录3│ ├── app/ # 应用程序组件4│ │ ├── app.component.ts # 根组件5│ │ ├── app.component.html6│ │ ├── app.component.scss7│ │ ├── app.component.spec.ts8│ │ ├── app.module.ts # 根模块9│ │ └── app-routing.module.ts10│ ├── assets/ # 静态资源11│ ├── environments/ # 环境配置12│ ├── favicon.ico13│ ├── index.html # 主HTML文件14│ ├── main.ts # 应用入口点15│ ├── polyfills.ts # 浏览器兼容性脚本16│ └── styles.scss # 全局样式17├── angular.json # Angular工作区配置18├── package.json # NPM依赖19├── tsconfig.json # TypeScript配置20└── README.md3. 组件基础
组件是Angular应用的核心构建块,每个组件包含:
- 一个HTML模板定义视图
- 一个TypeScript类控制逻辑和数据
- CSS样式定义外观
3.1 创建组件
使用Angular CLI创建组件:
bash
1ng generate component hero2# 简写形式3ng g c hero组件文件结构:
1src/app/hero/2├── hero.component.ts # 组件类和元数据3├── hero.component.html # HTML模板4├── hero.component.scss # 样式(SCSS)5└── hero.component.spec.ts # 单元测试基本组件结构:
hero.component.ts
typescript
1import { Component, OnInit } from '@angular/core';23@Component({4 selector: 'app-hero',5 templateUrl: './hero.component.html',6 styleUrls: ['./hero.component.scss']7})8export class HeroComponent implements OnInit {9 // 组件属性10 name: string = 'Windstorm';11 12 constructor() { }1314 ngOnInit(): void {15 // 组件初始化逻辑16 }17 18 // 组件方法19 greet(): string {20 return `Hello, ${this.name}!`;21 }22}3.2 组件模板语法
Angular模板支持多种绑定语法:
hero.component.html
html
1<div class="hero-card">2 <!-- 插值表达式 -->3 <h2>{{ name }}</h2>4 5 <!-- 属性绑定 -->6 <img [src]="heroImageUrl" [alt]="name">7 8 <!-- 事件绑定 -->9 <button (click)="onSelect()">选择</button>10 11 <!-- 双向绑定 (需要FormsModule) -->12 <input [(ngModel)]="name">13 14 <!-- 模板引用变量 -->15 <input #heroInput>16 <button (click)="updateName(heroInput.value)">更新名称</button>17 18 <!-- 条件渲染 -->19 <div *ngIf="isSelected">已选择!</div>20 21 <!-- 列表渲染 -->22 <ul>23 <li *ngFor="let power of powers; let i = index">24 {{ i + 1 }}. {{ power }}25 </li>26 </ul>27 28 <!-- 类绑定 -->29 <div [class.selected]="isSelected">状态展示</div>30 31 <!-- 样式绑定 -->32 <div [style.color]="isActive ? 'green' : 'red'">状态颜色</div>33</div>3.3 生命周期钩子
Angular组件有一系列生命周期钩子:
| 钩子 | 时机 | 用途 |
|---|---|---|
ngOnChanges | 输入属性变化时 | 响应输入属性变化 |
ngOnInit | 组件初始化时 | 执行初始化逻辑 |
ngDoCheck | 变更检测运行时 | 自定义变更检测 |
ngAfterContentInit | 内容投影初始化后 | 处理投影内容 |
ngAfterContentChecked | 内容投影检查后 | 检查投影内容变化 |
ngAfterViewInit | 视图初始化后 | 处理子视图逻辑 |
ngAfterViewChecked | 视图检查后 | 检查视图变化 |
ngOnDestroy | 组件销毁前 | 清理资源 |
示例实现:
typescript
1import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges, Input } from '@angular/core';23@Component({4 selector: 'app-lifecycle',5 template: `<div>{{ data }}</div>`6})7export class LifecycleComponent implements OnInit, OnChanges, OnDestroy {8 @Input() data: string;9 10 constructor() {11 console.log('Constructor called');12 }13 14 ngOnChanges(changes: SimpleChanges) {15 console.log('ngOnChanges called', changes);16 }17 18 ngOnInit() {19 console.log('ngOnInit called');20 }21 22 ngOnDestroy() {23 console.log('ngOnDestroy called');24 // 清理代码(取消订阅、清除定时器等)25 }26}4. 指令基础
Angular有三种类型的指令:
- 组件指令: 带有模板的指令
- 结构型指令: 修改DOM结构(*ngIf, *ngFor, *ngSwitch)
- 属性型指令: 修改元素的外观或行为(ngClass, ngStyle)
4.1 内置指令
内置指令示例
html
1<!-- NgIf -->2<div *ngIf="isLoggedIn; else loginBlock">3 欢迎回来,{{ username }}!4</div>5<ng-template #loginBlock>6 请登录以继续7</ng-template>89<!-- NgFor -->10<ul>11 <li *ngFor="let item of items; let i = index; trackBy: trackByFn">12 {{ i + 1 }} - {{ item.name }}13 </li>14</ul>1516<!-- NgSwitch -->17<div [ngSwitch]="userRole">18 <div *ngSwitchCase="'admin'">管理员面板</div>19 <div *ngSwitchCase="'editor'">编辑面板</div>20 <div *ngSwitchDefault>用户面板</div>21</div>2223<!-- NgClass -->24<div [ngClass]="{25 'active': isActive,26 'disabled': isDisabled,27 'highlight': isHighlighted28}">样式控制</div>2930<!-- NgStyle -->31<div [ngStyle]="{32 'color': textColor,33 'font-size.px': fontSize,34 'background-color': isImportant ? 'red' : 'transparent'35}">样式控制</div>4.2 自定义指令
创建自定义指令:
bash
1ng generate directive highlight2# 简写3ng g d highlight实现自定义指令:
highlight.directive.ts
typescript
1import { Directive, ElementRef, HostListener, Input } from '@angular/core';23@Directive({4 selector: '[appHighlight]'5})6export class HighlightDirective {7 @Input('appHighlight') highlightColor: string = 'yellow';8 @Input() defaultColor: string = '';9 10 constructor(private el: ElementRef) {}11 12 @HostListener('mouseenter') onMouseEnter() {13 this.highlight(this.highlightColor || 'yellow');14 }15 16 @HostListener('mouseleave') onMouseLeave() {17 this.highlight(this.defaultColor);18 }19 20 private highlight(color: string) {21 this.el.nativeElement.style.backgroundColor = color;22 }23}使用自定义指令:
html
1<p appHighlight="lightblue" defaultColor="white">2 鼠标悬停时会高亮显示3</p>5. 模块系统
Angular应用由模块组成,主要模块类型包括:
- 根模块(AppModule): 应用的引导模块
- 功能模块: 关注特定功能的模块
- 共享模块: 提供共享组件/指令/管道的模块
- 核心模块: 提供单例服务的模块
- 路由模块: 配置路由的模块
5.1 创建功能模块
bash
1ng generate module admin2# 简写3ng g m admin模块定义示例:
admin.module.ts
typescript
1import { NgModule } from '@angular/core';2import { CommonModule } from '@angular/common';3import { FormsModule } from '@angular/forms';45import { AdminRoutingModule } from './admin-routing.module';6import { AdminDashboardComponent } from './admin-dashboard/admin-dashboard.component';7import { UserManagementComponent } from './user-management/user-management.component';89@NgModule({10 imports: [11 CommonModule,12 FormsModule,13 AdminRoutingModule14 ],15 declarations: [16 AdminDashboardComponent,17 UserManagementComponent18 ],19 exports: [20 AdminDashboardComponent // 导出给其他模块使用21 ]22})23export class AdminModule { }5.2 共享模块
创建共享模块:
shared.module.ts
typescript
1import { NgModule } from '@angular/core';2import { CommonModule } from '@angular/common';3import { FormsModule } from '@angular/forms';45import { HighlightDirective } from './directives/highlight.directive';6import { ButtonComponent } from './components/button/button.component';7import { CardComponent } from './components/card/card.component';8import { TruncatePipe } from './pipes/truncate.pipe';910@NgModule({11 imports: [12 CommonModule13 ],14 declarations: [15 HighlightDirective,16 ButtonComponent,17 CardComponent,18 TruncatePipe19 ],20 exports: [21 CommonModule,22 FormsModule,23 HighlightDirective,24 ButtonComponent,25 CardComponent,26 TruncatePipe27 ]28})29export class SharedModule { }6. 服务与依赖注入
服务是一个广义的类别,用于封装可重用的逻辑,如数据访问、日志记录和业务规则。
6.1 创建服务
bash
1ng generate service hero2# 简写3ng g s hero服务实现:
hero.service.ts
typescript
1import { Injectable } from '@angular/core';2import { HttpClient } from '@angular/common/http';3import { Observable, of } from 'rxjs';4import { catchError, map, tap } from 'rxjs/operators';56import { Hero } from './hero.model';7import { LoggingService } from './logging.service';89@Injectable({10 providedIn: 'root' // 注册为应用级单例11})12export class HeroService {13 private apiUrl = 'api/heroes';14 15 constructor(16 private http: HttpClient,17 private loggingService: LoggingService18 ) { }19 20 getHeroes(): Observable<Hero[]> {21 return this.http.get<Hero[]>(this.apiUrl).pipe(22 tap(_ => this.log('fetched heroes')),23 catchError(this.handleError<Hero[]>('getHeroes', []))24 );25 }26 27 getHero(id: number): Observable<Hero> {28 const url = `${this.apiUrl}/${id}`;29 return this.http.get<Hero>(url).pipe(30 tap(_ => this.log(`fetched hero id=${id}`)),31 catchError(this.handleError<Hero>(`getHero id=${id}`))32 );33 }34 35 private log(message: string) {36 this.loggingService.log(`HeroService: ${message}`);37 }38 39 private handleError<T>(operation = 'operation', result?: T) {40 return (error: any): Observable<T> => {41 console.error(error);42 this.log(`${operation} failed: ${error.message}`);43 return of(result as T);44 };45 }46}6.2 依赖注入
依赖注入(DI)是一种设计模式,用于实现控制反转。Angular的DI系统通过以下方式工作:
- 提供者(Provider): 告诉Angular如何创建服务实例
- 注入器(Injector): 维护服务实例并在需要时创建
- 依赖(Dependency): 服务或对象,注入到类中
提供服务的方式:
typescript
1// 1. 在@Injectable装饰器中提供2@Injectable({3 providedIn: 'root' // 应用级单例4})5export class LoggingService { }67// 2. 在模块中提供8@NgModule({9 providers: [HeroService]10})11export class AppModule { }1213// 3. 在组件中提供(组件及其子组件可用)14@Component({15 providers: [UserService]16})17export class UserComponent { }7. 表单处理
Angular提供两种表单处理方法:模板驱动表单和响应式表单。
7.1 模板驱动表单
基于模板指令,简单直观,适合简单场景:
contact-form.component.ts
typescript
1import { Component } from '@angular/core';2import { NgForm } from '@angular/forms';34interface ContactForm {5 name: string;6 email: string;7 message: string;8}910@Component({11 selector: 'app-contact-form',12 templateUrl: './contact-form.component.html'13})14export class ContactFormComponent {15 model: ContactForm = {16 name: '',17 email: '',18 message: ''19 };20 21 submitted = false;22 23 onSubmit(form: NgForm) {24 if (form.valid) {25 console.log('Form submitted', this.model);26 this.submitted = true;27 // 处理表单提交...28 }29 }30}contact-form.component.html
html
1<div class="container">2 <div *ngIf="submitted" class="alert alert-success">3 表单已提交成功!4 </div>56 <form #contactForm="ngForm" (ngSubmit)="onSubmit(contactForm)" novalidate>7 <div class="form-group">8 <label for="name">姓名</label>9 <input10 type="text"11 class="form-control"12 id="name"13 name="name"14 [(ngModel)]="model.name"15 required16 #name="ngModel">17 <div [hidden]="name.valid || name.pristine" class="alert alert-danger">18 姓名是必填项19 </div>20 </div>2122 <div class="form-group">23 <label for="email">电子邮箱</label>24 <input25 type="email"26 class="form-control"27 id="email"28 name="email"29 [(ngModel)]="model.email"30 required31 email32 #email="ngModel">33 <div [hidden]="email.valid || email.pristine" class="alert alert-danger">34 请输入有效的电子邮箱35 </div>36 </div>3738 <div class="form-group">39 <label for="message">留言</label>40 <textarea41 class="form-control"42 id="message"43 name="message"44 rows="5"45 [(ngModel)]="model.message"46 required47 minlength="10"48 #message="ngModel"></textarea>49 <div [hidden]="message.valid || message.pristine" class="alert alert-danger">50 留言至少需要10个字符51 </div>52 </div>5354 <button type="submit" class="btn btn-primary" [disabled]="!contactForm.form.valid">55 提交56 </button>57 </form>58</div>7.2 响应式表单
基于显式模型定义,更加灵活强大,适合复杂场景:
registration.component.ts
typescript
1import { Component, OnInit } from '@angular/core';2import { FormBuilder, FormGroup, FormControl, Validators, AbstractControl, ValidationErrors } from '@angular/forms';34@Component({5 selector: 'app-registration',6 templateUrl: './registration.component.html'7})8export class RegistrationComponent implements OnInit {9 registrationForm: FormGroup;10 submitted = false;11 12 constructor(private fb: FormBuilder) { }13 14 ngOnInit() {15 this.registrationForm = this.fb.group({16 name: ['', [Validators.required, Validators.minLength(3)]],17 email: ['', [Validators.required, Validators.email]],18 password: ['', [Validators.required, Validators.minLength(8)]],19 confirmPassword: ['', Validators.required],20 terms: [false, Validators.requiredTrue]21 }, {22 validators: this.passwordMatchValidator23 });24 }25 26 // 自定义验证器27 passwordMatchValidator(control: AbstractControl): ValidationErrors | null {28 const password = control.get('password');29 const confirmPassword = control.get('confirmPassword');30 31 if (password?.value !== confirmPassword?.value) {32 return { 'passwordMismatch': true };33 }34 35 return null;36 }37 38 get f() {39 return this.registrationForm.controls;40 }41 42 onSubmit() {43 this.submitted = true;44 45 if (this.registrationForm.invalid) {46 return;47 }48 49 console.log('Registration form submitted', this.registrationForm.value);50 // 处理表单提交...51 }52}registration.component.html
html
1<div class="container">2 <h2>用户注册</h2>3 4 <form [formGroup]="registrationForm" (ngSubmit)="onSubmit()">5 <div class="form-group">6 <label for="name">姓名</label>7 <input8 type="text"9 id="name"10 formControlName="name"11 class="form-control"12 [ngClass]="{ 'is-invalid': submitted && f.name.errors }">13 <div *ngIf="submitted && f.name.errors" class="invalid-feedback">14 <div *ngIf="f.name.errors.required">姓名是必填项</div>15 <div *ngIf="f.name.errors.minlength">姓名至少需要3个字符</div>16 </div>17 </div>18 19 <div class="form-group">20 <label for="email">电子邮箱</label>21 <input22 type="email"23 id="email"24 formControlName="email"25 class="form-control"26 [ngClass]="{ 'is-invalid': submitted && f.email.errors }">27 <div *ngIf="submitted && f.email.errors" class="invalid-feedback">28 <div *ngIf="f.email.errors.required">电子邮箱是必填项</div>29 <div *ngIf="f.email.errors.email">请输入有效的电子邮箱地址</div>30 </div>31 </div>32 33 <div class="form-group">34 <label for="password">密码</label>35 <input36 type="password"37 id="password"38 formControlName="password"39 class="form-control"40 [ngClass]="{ 'is-invalid': submitted && f.password.errors }">41 <div *ngIf="submitted && f.password.errors" class="invalid-feedback">42 <div *ngIf="f.password.errors.required">密码是必填项</div>43 <div *ngIf="f.password.errors.minlength">密码至少需要8个字符</div>44 </div>45 </div>46 47 <div class="form-group">48 <label for="confirmPassword">确认密码</label>49 <input50 type="password"51 id="confirmPassword"52 formControlName="confirmPassword"53 class="form-control"54 [ngClass]="{ 'is-invalid': submitted && (f.confirmPassword.errors || registrationForm.errors?.passwordMismatch) }">55 <div *ngIf="submitted && (f.confirmPassword.errors || registrationForm.errors?.passwordMismatch)" class="invalid-feedback">56 <div *ngIf="f.confirmPassword.errors?.required">确认密码是必填项</div>57 <div *ngIf="registrationForm.errors?.passwordMismatch">两次输入的密码不匹配</div>58 </div>59 </div>60 61 <div class="form-group form-check">62 <input63 type="checkbox"64 id="terms"65 formControlName="terms"66 class="form-check-input"67 [ngClass]="{ 'is-invalid': submitted && f.terms.errors }">68 <label for="terms" class="form-check-label">我同意所有条款和条件</label>69 <div *ngIf="submitted && f.terms.errors" class="invalid-feedback">70 <div *ngIf="f.terms.errors.required">必须同意条款和条件才能继续</div>71 </div>72 </div>73 74 <button type="submit" class="btn btn-primary">注册</button>75 </form>76</div>8. HTTP通信
Angular提供HttpClient用于与服务器通信。
data.service.ts
typescript
1import { Injectable } from '@angular/core';2import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';3import { Observable, throwError } from 'rxjs';4import { catchError, retry, map } from 'rxjs/operators';56import { Product } from './product.model';78@Injectable({9 providedIn: 'root'10})11export class DataService {12 private apiUrl = 'https://api.example.com/products';13 14 constructor(private http: HttpClient) { }15 16 getProducts(category?: string): Observable<Product[]> {17 let params = new HttpParams();18 if (category) {19 params = params.set('category', category);20 }21 22 return this.http.get<Product[]>(this.apiUrl, { params }).pipe(23 retry(2), // 失败时重试24 catchError(this.handleError)25 );26 }27 28 getProduct(id: number): Observable<Product> {29 return this.http.get<Product>(`${this.apiUrl}/${id}`).pipe(30 catchError(this.handleError)31 );32 }33 34 createProduct(product: Product): Observable<Product> {35 const httpOptions = {36 headers: new HttpHeaders({37 'Content-Type': 'application/json'38 })39 };40 41 return this.http.post<Product>(this.apiUrl, product, httpOptions).pipe(42 catchError(this.handleError)43 );44 }45 46 updateProduct(product: Product): Observable<Product> {47 return this.http.put<Product>(`${this.apiUrl}/${product.id}`, product).pipe(48 catchError(this.handleError)49 );50 }51 52 deleteProduct(id: number): Observable<void> {53 return this.http.delete<void>(`${this.apiUrl}/${id}`).pipe(54 catchError(this.handleError)55 );56 }57 58 private handleError(error: any) {59 let errorMessage = '';60 if (error.error instanceof ErrorEvent) {61 // 客户端错误62 errorMessage = `Error: ${error.error.message}`;63 } else {64 // 服务器端错误65 errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;66 }67 console.error(errorMessage);68 return throwError(() => new Error(errorMessage));69 }70}在组件中使用HTTP服务:
product-list.component.ts
typescript
1import { Component, OnInit } from '@angular/core';2import { DataService } from '../services/data.service';3import { Product } from '../models/product.model';45@Component({6 selector: 'app-product-list',7 templateUrl: './product-list.component.html'8})9export class ProductListComponent implements OnInit {10 products: Product[] = [];11 loading = false;12 error = '';13 14 constructor(private dataService: DataService) { }15 16 ngOnInit(): void {17 this.getProducts();18 }19 20 getProducts(): void {21 this.loading = true;22 this.dataService.getProducts()23 .subscribe({24 next: (data) => {25 this.products = data;26 this.loading = false;27 },28 error: (error) => {29 this.error = error;30 this.loading = false;31 }32 });33 }34 35 deleteProduct(id: number): void {36 if (confirm('确定要删除这个产品吗?')) {37 this.dataService.deleteProduct(id)38 .subscribe({39 next: () => {40 this.products = this.products.filter(p => p.id !== id);41 },42 error: (error) => {43 this.error = error;44 }45 });46 }47 }48}9. Angular最佳实践
9.1 目录结构
推荐按功能模块组织目录:
1app/2├── core/ # 核心功能(单例服务、拦截器等)3│ ├── services/4│ ├── interceptors/5│ └── core.module.ts6├── shared/ # 共享组件、指令、管道7│ ├── components/8│ ├── directives/9│ ├── pipes/10│ └── shared.module.ts11├── features/ # 按功能划分的模块12│ ├── home/13│ ├── auth/14│ ├── products/15│ └── admin/16├── models/ # 数据模型/接口17├── utils/ # 工具函数18├── app-routing.module.ts19├── app.component.ts20└── app.module.ts9.2 性能优化
- 使用OnPush变更检测:减少不必要的变更检测
- 延迟加载模块:提高初始加载性能
- 追踪NgFor:为大列表使用trackBy函数
- 纯管道:使用纯管道进行数据转换
- 使用AOT编译:提前编译提高性能
typescript
1// OnPush变更检测2@Component({3 changeDetection: ChangeDetectionStrategy.OnPush4})56// NgFor优化7<div *ngFor="let item of items; trackBy: trackById"></div>89trackById(index: number, item: any): number {10 return item.id;11}9.3 代码风格指南
- 遵循官方Angular风格指南
- 使用类型安全(TypeScript接口和类型)
- 将业务逻辑从组件移至服务
- 避免在模板中使用复杂表达式
- 组件保持小巧、聚焦且可重用
- 使用RxJS操作符简化异步代码
参与讨论