with one click
angular
Angular 框架编程助手,提供组件开发、路由、表单、HTTP、依赖注入等方面的指导和代码生成
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Angular 框架编程助手,提供组件开发、路由、表单、HTTP、依赖注入等方面的指导和代码生成
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | angular |
| description | Angular 框架编程助手,提供组件开发、路由、表单、HTTP、依赖注入等方面的指导和代码生成 |
| disable-model-invocation | false |
| allowed-tools | Read, Write, Edit, Grep, Glob |
| argument-hint | ["Angular 开发任务"] |
本技能提供 Angular 框架开发的全面支持,包括组件、指令、服务、路由、表单、HTTP 通信等核心功能的指导和代码生成。
组件是 Angular 应用的基本构建块,控制屏幕上的一块区域(称为视图)。
import { Component } from '@angular/core';
@Component({
selector: 'app-my-component',
template: `
<h1>{{ title }}</h1>
<p>{{ description }}</p>
`,
styles: [`
h1 { color: #333; }
p { color: #666; }
`],
standalone: true
})
export class MyComponent {
title = 'Hello Angular';
description = 'This is a simple component';
}
selector: CSS 选择器,用于在模板中标识组件(如 'app-my-component')template: 内联 HTML 模板templateUrl: 外部 HTML 模板文件路径styles: 内联 CSS 样式styleUrls: 外部 CSS 样式文件路径standalone: 标记为独立组件(Angular 14+)import { Component, OnInit, OnChanges, OnDestroy } from '@angular/core';
@Component({...})
export class MyComponent implements OnInit, OnChanges, OnDestroy {
// 在输入属性初始化后调用一次
ngOnInit(): void {
console.log('Component initialized');
}
// 在输入属性变化前调用
ngOnChanges(changes: SimpleChanges): void {
console.log('Input changed', changes);
}
// 在组件销毁前调用
ngOnDestroy(): void {
console.log('Component destroyed');
}
}
主要生命周期钩子:
OnInit: 组件初始化完成后调用OnChanges: 输入属性变化时调用DoCheck: 每次变更检测时调用AfterContentInit: 内容投影完成后调用AfterViewInit: 视图初始化完成后调用AfterContentChecked: 内容检查完成后调用AfterViewChecked: 视图检查完成后调用OnDestroy: 组件销毁前调用输入属性 (@Input): 从父组件接收数据
import { Component, Input } from '@angular/core';
@Component({...})
export class ChildComponent {
@Input() message: string = '';
@Input() user?: User;
}
输出属性 (@Output): 向父组件发送事件
import { Component, Output, EventEmitter } from '@angular/core';
@Component({...})
export class ChildComponent {
@Output() messageChanged = new EventEmitter<string>();
@Output() buttonClicked = new EventEmitter<void>();
sendMessage() {
this.messageChanged.emit('Hello from child');
}
handleClick() {
this.buttonClicked.emit();
}
}
父组件使用:
<app-child
[message]="parentMessage"
[user]="currentUser"
(messageChanged)="handleMessageChange($event)"
(buttonClicked)="handleButtonClick()">
</app-child>
独立组件不依赖 NgModule,更加简洁和模块化。
@Component({
selector: 'app-standalone',
template: `
<h1>{{ title }}</h1>
<button (click)="increment()">Count: {{ count() }}</button>
`,
imports: [CommonModule, FormsModule], // 导入所需依赖
standalone: true
})
export class StandaloneComponent {
title = 'Standalone Component';
count = signal(0);
increment() {
this.count.update(c => c + 1);
}
}
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, Routes } from '@angular/router';
import { AppComponent } from './app/app.component';
const routes: Routes = [
{ path: '', component: AppComponent },
// 其他路由...
];
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes)
]
});
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root' // 在根级别提供服务
})
export class DataService {
private apiUrl = 'https://api.example.com';
getData(): Observable<any> {
return this.http.get(this.apiUrl);
}
constructor(private http: HttpClient) {}
}
import { Component, inject } from '@angular/core';
import { DataService } from './data.service';
@Component({...})
export class MyComponent {
// 构造函数注入(传统方式)
constructor(private dataService: DataService) {}
// 函数注入(现代方式)
private http = inject(HttpClient);
}
// ClassProvider - 提供类实例
{ provide: DataService, useClass: DataService }
// ValueProvider - 提供静态值
{ provide: 'API_URL', useValue: 'https://api.example.com' }
// FactoryProvider - 使用工厂函数
{
provide: 'CONFIG',
useFactory: (dataService: DataService) => {
return dataService.getConfig();
},
deps: [DataService]
}
// ExistingProvider - 别名到现有 provider
{ provide: AbstractService, useExisting: ConcreteService }
import { InjectionToken } from '@angular/core';
export const API_URL = new InjectionToken<string>('API_URL');
// 提供值
{ provide: API_URL, useValue: 'https://api.example.com' }
// 注入值
constructor(@Inject(API_URL) private apiUrl: string) {}
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: 'users/:id', component: UserDetailComponent },
{ path: '**', component: PageNotFoundComponent } // 通配路由
];
// NgModule 方式
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
// Standalone 方式
bootstrapApplication(AppComponent, {
providers: [provideRouter(routes)]
});
模板导航:
<a routerLink="/about">About</a>
<a [routerLink]="['/users', userId]">User Details</a>
<router-outlet></router-outlet>
编程导航:
import { Router, ActivatedRoute } from '@angular/router';
constructor(
private router: Router,
private route: ActivatedRoute
) {}
// 绝对导航
navigateToUsers() {
this.router.navigate(['/users']);
}
// 相对导航
navigateToDetails(id: string) {
this.router.navigate(['../', id], { relativeTo: this.route });
}
// 带查询参数
navigateWithParams() {
this.router.navigate(['/search'], { queryParams: { q: 'angular' } });
}
import { ActivatedRoute } from '@angular/router';
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// 获取快照参数
const id = this.route.snapshot.paramMap.get('id');
// 订阅参数变化
this.route.paramMap.subscribe(params => {
const id = params.get('id');
});
// 获取查询参数
this.route.queryParamMap.subscribe(params => {
const page = params.get('page');
});
}
// CanActivate - 激活守卫
export const canActivateGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService);
if (authService.isAuthenticated()) {
return true;
}
return inject(Router).createUrlTree(['/login']);
};
// 使用守卫
const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [canActivateGuard]
}
];
Angular 支持两种表单方式:响应式表单和模板驱动表单。
更适合复杂场景,提供更好的可测试性和可扩展性。
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({...})
export class ReactiveFormComponent {
userForm: FormGroup;
constructor(private fb: FormBuilder) {
this.userForm = this.fb.group({
name: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
age: [null, [Validators.min(18), Validators.max(120)]],
address: this.fb.group({
street: [''],
city: [''],
zip: ['']
}),
hobbies: this.fb.array([])
});
}
// 添加爱好
addHobby() {
const hobbies = this.userForm.get('hobbies') as FormArray;
hobbies.push(this.fb.control(''));
}
// 提交表单
onSubmit() {
if (this.userForm.valid) {
console.log(this.userForm.value);
}
}
}
模板:
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<input formControlName="name" placeholder="Name">
<div *ngIf="userForm.get('name')?.errors?.['required']">Name is required</div>
<div *ngIf="userForm.get('name')?.errors?.['minlength']">Name must be at least 3 characters</div>
<input formControlName="email" placeholder="Email">
<div *ngIf="userForm.get('email')?.errors?.['email']">Invalid email format</div>
<div formGroupName="address">
<input formControlName="street" placeholder="Street">
<input formControlName="city" placeholder="City">
<input formControlName="zip" placeholder="Zip">
</div>
<div formArrayName="hobbies">
<div *ngFor="let hobby of userForm.get('hobbies')?.controls; let i = index">
<input [formControlName]="i" placeholder="Hobby">
</div>
</div>
<button type="submit" [disabled]="userForm.invalid">Submit</button>
</form>
更简单,适合小型表单。
import { Component } from '@angular/core';
@Component({...})
export class TemplateFormComponent {
user = {
name: '',
email: ''
};
onSubmit() {
console.log(this.user);
}
}
模板:
<form #userForm="ngForm" (ngSubmit)="onSubmit()">
<input [(ngModel)]="user.name" name="name" required minlength="3" #name="ngModel">
<div *ngIf="name.invalid && name.touched">
<div *ngIf="name.errors?.['required']">Name is required</div>
<div *ngIf="name.errors?.['minlength']">Name must be at least 3 characters</div>
</div>
<input [(ngModel)]="user.email" name="email" required email #email="ngModel">
<div *ngIf="email.invalid && email.touched">
<div *ngIf="email.errors?.['required']">Email is required</div>
<div *ngIf="email.errors?.['email']">Invalid email format</div>
</div>
<button type="submit" [disabled]="userForm.invalid">Submit</button>
</form>
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({...})
export class ApiService {
private apiUrl = 'https://api.example.com';
constructor(private http: HttpClient) {}
// GET 请求
getData(): Observable<any[]> {
return this.http.get<any[]>(`${this.apiUrl}/data`);
}
// GET 请求带参数
getDataById(id: string): Observable<any> {
return this.http.get<any>(`${this.apiUrl}/data/${id}`);
}
// POST 请求
createData(data: any): Observable<any> {
return this.http.post<any>(`${this.apiUrl}/data`, data);
}
// PUT 请求
updateData(id: string, data: any): Observable<any> {
return this.http.put<any>(`${this.apiUrl}/data/${id}`, data);
}
// DELETE 请求
deleteData(id: string): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/data/${id}`);
}
// GET 请求带查询参数
searchData(params: any): Observable<any[]> {
return this.http.get<any[]>(`${this.apiUrl}/data`, { params });
}
}
import { Observable, of, throwError } from 'rxjs';
import { catchError, map, tap, retry } from 'rxjs/operators';
getData(): Observable<any[]> {
return this.http.get<any[]>(`${this.apiUrl}/data`).pipe(
retry(3), // 重试 3 次
map(data => data.map(item => ({ ...item, formatted: true }))), // 转换数据
tap(data => console.log('Data loaded:', data)), // 副作用
catchError(error => {
console.error('Error occurred:', error);
return throwError(() => error); // 或返回默认值: of([])
})
);
}
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// 添加认证头
const token = localStorage.getItem('authToken');
const authReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
});
return next.handle(authReq);
}
}
// 配置拦截器
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@Input('appHighlight') highlightColor = 'yellow';
constructor(private el: ElementRef) {}
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.highlightColor);
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight(null);
}
private highlight(color: string | null) {
this.el.nativeElement.style.backgroundColor = color;
}
}
使用:
<p appHighlight>Hover over me!</p>
<p [appHighlight]="'red'">Custom color!</p>
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appUnless]'
})
export class UnlessDirective {
private hasView = false;
constructor(
private templateRef: TemplateRef<any>,
private viewContainer: ViewContainerRef
) {}
@Input() set appUnless(condition: boolean) {
if (!condition && !this.hasView) {
this.viewContainer.createEmbeddedView(this.templateRef);
this.hasView = true;
} else if (condition && this.hasView) {
this.viewContainer.clear();
this.hasView = false;
}
}
}
使用:
<div *appUnless="condition">Show unless condition is true</div>
@if (isLoggedIn) {
<p>Welcome back!</p>
} @else if (isGuest) {
<p>Welcome guest!</p>
} @else {
<p>Please login</p>
}
@for (item of items; track item.id; let i = $index) {
<div>{{ i + 1 }}. {{ item.name }}</div>
} @empty {
<div>No items found</div>
}
@switch (status) {
@case ('success') {
<p>Success!</p>
}
@case ('error') {
<p>Error!</p>
}
@default {
<p>Unknown status</p>
}
}
自动订阅 observable 或 promise。
@Component({
template: `
@if (data$ | async; as data) {
<p>{{ data.name }}</p>
}
`
})
export class MyComponent {
data$: Observable<Data>;
constructor(private service: DataService) {
this.data$ = service.getData();
}
}
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'truncate',
standalone: true
})
export class TruncatePipe implements PipeTransform {
transform(value: string, limit: number = 20): string {
if (!value) return '';
return value.length > limit ? value.substring(0, limit) + '...' : value;
}
}
使用:
<p>{{ longText | truncate:50 }}</p>
Signals 提供了新的响应式方式来管理状态。
import { Component, signal, computed, effect } from '@angular/core';
@Component({...})
export class SignalComponent {
// 信号
count = signal(0);
name = signal('Angular');
// 计算信号
doubleCount = computed(() => this.count() * 2);
greeting = computed(() => `Hello, ${this.name()}!`);
constructor() {
// 副作用
effect(() => {
console.log('Count changed:', this.count());
});
}
increment() {
this.count.update(c => c + 1);
}
setName(newName: string) {
this.name.set(newName);
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent } from './my.component';
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();
});
it('should display title', () => {
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello Angular');
});
});
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
describe('ApiService', () => {
let service: ApiService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [ApiService]
});
service = TestBed.inject(ApiService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
it('should fetch data', () => {
const mockData = [{ id: 1, name: 'Test' }];
service.getData().subscribe(data => {
expect(data).toEqual(mockData);
});
const req = httpMock.expectOne('https://api.example.com/data');
expect(req.request.method).toBe('GET');
req.flush(mockData);
});
});
OnPush 策略使用 AsyncPipe 或在组件中订阅:
// 推荐:使用 AsyncPipe
data$ = this.http.get('/api/data');
// 或:手动订阅(记得取消订阅)
private destroy$ = new Subject<void>();
ngOnInit() {
this.http.get('/api/data')
.pipe(takeUntil(this.destroy$))
.subscribe(data => {
this.data = data;
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
ChangeDetectionStrategy.OnPush// 核心
import { Component, Directive, Pipe, Injectable, NgModule } from '@angular/core';
// 常用指令
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
// HTTP
import { HttpClientModule } from '@angular/common/http';
// 路由
import { RouterModule, Routes } from '@angular/router';
// 动画
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
// RxJS
import { Observable, of, Subject } from 'rxjs';
import { map, filter, switchMap, tap, catchError, retry } from 'rxjs/operators';
ng help生成符合 Conventional Commits 标准的 git commit 信息(中文)。在需要提交代码时使用
NG-ZORRO (Ant Design) Angular UI 组件库开发助手,提供超过 70 个企业级 UI 组件的使用指导和代码生成
同步服务器 API 定义文件。在进行代码分析前自动调用,确保服务器接口定义保持最新