跳到主要内容

Angular 速查表

本文档汇总了 Angular 开发中常用的语法和知识点,方便快速查阅。

项目命令

# 创建项目
ng new project-name

# 创建组件
ng g c component-name

# 创建服务
ng g s service-name

# 创建指令
ng g d directive-name

# 创建管道
ng g p pipe-name

# 启动开发服务器
ng serve

# 构建项目
ng build

# 运行测试
ng test

# 代码格式化
ng lint

组件

基本组件结构

import { Component } from '@angular/core';

@Component({
selector: 'app-example',
standalone: true,
imports: [CommonModule],
template: `<p>{{ message }}</p>`,
styles: [`p { color: blue; }`]
})
export class ExampleComponent {
message = 'Hello Angular';
}

组件通信

// 输入属性
@Input() name = '';
@Input({ required: true }) id!: number;

// Signal 输入
name = input<string>();
age = input.required<number>();

// 输出属性
@Output() valueChange = new EventEmitter<string>();

// Model(双向绑定)
value = model<number>(0);

生命周期

ngOnChanges(changes: SimpleChanges) { }
ngOnInit() { }
ngDoCheck() { }
ngAfterContentInit() { }
ngAfterContentChecked() { }
ngAfterViewInit() { }
ngAfterViewChecked() { }
ngOnDestroy() { }

Signals

import { signal, computed, effect } from '@angular/core';

// 可写信号
count = signal(0);
count.set(5);
count.update(v => v + 1);

// 计算信号
double = computed(() => this.count() * 2);

// 效果
effect(() => {
console.log('Count changed:', this.count());
});

模板语法

数据绑定

<!-- 插值 -->
<p>{{ name }}</p>

<!-- 属性绑定 -->
<img [src]="imageUrl">
<div [class.active]="isActive">
<div [style.color]="textColor">

<!-- 事件绑定 -->
<button (click)="onClick()">点击</button>
<input (keyup.enter)="onEnter()">

<!-- 双向绑定 -->
<input [(ngModel)]="name">

控制流

<!-- 条件渲染 -->
@if (show) {
<p>显示内容</p>
} @else {
<p>隐藏内容</p>
}

<!-- 循环 -->
@for (item of items; track item.id; let i = $index) {
<li>{{ i }}: {{ item.name }}</li>
} @empty {
<p>列表为空</p>
}

<!-- 开关 -->
@switch (status) {
@case ('loading') { <p>加载中</p> }
@case ('success') { <p>成功</p> }
@default { <p>默认</p> }
}

模板引用

<input #nameInput>
<button (click)="nameInput.focus()">聚焦</button>
<p>{{ nameInput.value }}</p>

指令

内置属性指令

<div [ngClass]="{ active: isActive }">
<div [ngStyle]="{ 'color': textColor, 'font-size': fontSize + 'px' }">

内置结构指令

<div *ngIf="show; else hidden">显示</div>
<ng-template #hidden><p>隐藏</p></ng-template>

<li *ngFor="let item of items; let i = index; trackBy: trackById">
{{ i }}: {{ item.name }}
</li>

<div [ngSwitch]="value">
<p *ngSwitchCase="'a'">A</p>
<p *ngSwitchDefault>其他</p>
</div>

自定义指令

@Directive({
selector: '[appHighlight]',
standalone: true
})
export class HighlightDirective {
@Input() appHighlight = 'yellow';

@HostBinding('style.backgroundColor') bgColor = '';

@HostListener('mouseenter') onMouseEnter() {
this.bgColor = this.appHighlight;
}

@HostListener('mouseleave') onMouseLeave() {
this.bgColor = '';
}
}

服务与依赖注入

创建服务

import { Injectable, inject } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);

getUsers() {
return this.http.get<User[]>('/api/users');
}
}

注入服务

// 推荐方式
userService = inject(UserService);

// 构造函数方式
constructor(private userService: UserService) {}

表单

响应式表单

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

// 使用 FormBuilder
form = this.fb.group({
name: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
address: this.fb.group({
city: [''],
street: ['']
}),
hobbies: this.fb.array([
this.fb.control('')
])
});

// 验证状态
form.valid
form.invalid
form.get('name')?.errors
form.get('name')?.touched
form.get('name')?.dirty

模板驱动表单

<form #form="ngForm" (ngSubmit)="onSubmit(form)">
<input name="name" [(ngModel)]="user.name" required minlength="3" #name="ngModel">
<p *ngIf="name.invalid && name.touched">名称无效</p>
</form>

路由

路由配置

export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'users/:id', component: UserDetailComponent },
{ path: 'admin', canActivate: [authGuard], loadChildren: () => import('./admin/routes') },
{ path: '**', redirectTo: '' }
];

路由导航

<a routerLink="/users">用户列表</a>
<a [routerLink]="['/users', userId]">用户详情</a>
<a routerLink="/users" [queryParams]="{ page: 1 }">用户列表</a>
<a routerLink="/home" routerLinkActive="active">首页</a>
// 编程式导航
router.navigate(['/users', 123]);
router.navigate(['/users'], { queryParams: { page: 1 } });

// 获取参数
route.snapshot.paramMap.get('id');
route.snapshot.queryParamMap.get('page');

路由守卫

// CanActivate
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
return auth.isLoggedIn() || inject(Router).createUrlTree(['/login']);
};

// CanDeactivate
export const canDeactivateGuard: CanDeactivateFn<CanComponentDeactivate> = (component) => {
return component.canDeactivate();
};

HTTP

基本请求

// GET
http.get<User[]>('/api/users');
http.get<User>('/api/users/1');

// POST
http.post<User>('/api/users', { name: '张三' });

// PUT
http.put<User>('/api/users/1', { name: '李四' });

// DELETE
http.delete<void>('/api/users/1');

拦截器

export const authInterceptor: HttpInterceptorFn = (req, next) => {
const auth = inject(AuthService);

const authReq = req.clone({
setHeaders: { Authorization: `Bearer ${auth.getToken()}` }
});

return next(authReq);
};

管道

内置管道

<!-- 日期 -->
{{ date | date:'yyyy-MM-dd' }}

<!-- 大小写 -->
{{ text | uppercase }}
{{ text | lowercase }}
{{ text | titlecase }}

<!-- 数字 -->
{{ num | number:'1.0-2' }}
{{ price | currency:'CNY' }}
{{ ratio | percent }}

<!-- 其他 -->
{{ obj | json }}
{{ arr | slice:0:5 }}
{{ data$ | async }}

自定义管道

@Pipe({
name: 'truncate',
standalone: true
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit = 20): string {
return value.length > limit ? value.substring(0, limit) + '...' : value;
}
}

常用 RxJS 操作符

import { of, from, interval, merge, combineLatest } from 'rxjs';
import { map, filter, tap, switchMap, catchError, debounceTime, distinctUntilChanged } from 'rxjs/operators';

// 创建 Observable
of(1, 2, 3);
from([1, 2, 3]);
interval(1000);

// 转换
pipe(
map(x => x * 2),
filter(x => x > 5),
tap(x => console.log(x))
);

// 组合
pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => http.get(`/search?q=${term}`))
);

// 错误处理
pipe(
catchError(error => of([]))
);

测试

组件测试

describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MyComponent]
}).compileComponents();

fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

服务测试

describe('MyService', () => {
let service: MyService;
let httpMock: HttpTestingController;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [MyService]
});
service = TestBed.inject(MyService);
httpMock = TestBed.inject(HttpTestingController);
});

it('should fetch data', () => {
service.getData().subscribe(data => {
expect(data).toEqual([]);
});
httpMock.expectOne('/api/data').flush([]);
});
});

常见问题

组件不更新

  • 检查是否调用了 fixture.detectChanges()
  • 检查 Signal 是否正确使用 () 读取
  • 检查是否在 Angular 区域外修改了数据

路由不工作

  • 检查 RouterOutlet 是否已导入
  • 检查路由配置是否正确
  • 检查 provideRouter 是否已添加

表单验证不生效

  • 检查是否导入了 ReactiveFormsModuleFormsModule
  • 检查验证器是否正确配置
  • 检查是否触发了 toucheddirty 状态

HTTP 请求失败

  • 检查是否配置了 provideHttpClient()
  • 检查 CORS 配置
  • 检查拦截器是否正确配置