Angular 编码风格¶
本文件使用 Angular 特定内容扩展 common/coding-style.md。
版本检查¶
在编写代码之前,务必确认项目的 Angular 版本 — 不同版本之间的功能差异很大。请运行 ng version 或查看 package.json。创建新项目时,除非用户指定,否则不要固定版本。
生成或修改 Angular 代码后,在结束前务必运行 ng build 以检测错误。
文件命名¶
遵循 Angular CLI 约定,每个文件只放置一个产物:
user-profile.component.ts+user-profile.component.html+user-profile.component.spec.tsuser.service.ts、auth.guard.ts、date-format.pipe.ts- 功能文件夹:
features/users/、features/auth/ - 使用 CLI 生成:
ng generate component features/users/user-card
组件¶
优先使用独立组件(v17+ 默认)。在所有新组件中使用 OnPush 变更检测。
@Component({
selector: 'app-user-card',
standalone: true,
imports: [RouterModule],
templateUrl: './user-card.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserCardComponent {
user = input.required<User>();
select = output<string>();
}
依赖注入¶
优先使用 inject() 而不是构造函数注入。构造函数应保持为空或完全删除。
// 正しい
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
private router = inject(Router);
}
// 誤り: コンストラクタ注入は冗長で、ツリーシェイキングが困難
constructor(private http: HttpClient, private router: Router) {}
对于非类依赖项,请使用 InjectionToken:
const API_URL = new InjectionToken<string>('API_URL');
// 提供:
{ provide: API_URL, useValue: 'https://api.example.com' }
// 使用:
private apiUrl = inject(API_URL);
信号¶
基本原语¶
count = signal(0);
doubled = computed(() => this.count() * 2);
increment() {
this.count.update(n => n + 1);
}
linkedSignal — 可写派生状态¶
对于需要在源变更时重置或适应,但可独立写入的信号,请使用 linkedSignal:
selectedOption = linkedSignal(() => this.options()[0]);
// options が変更されると最初のオプションにリセットされるが、ユーザーはオーバーライド可能
resource — 将异步数据转换为信号¶
要无需手动订阅即可响应式获取异步数据,请使用 resource():
userResource = resource({
request: () => ({ id: this.userId() }),
loader: ({ request }) => fetch(`/api/users/${request.id}`).then(r => r.json()),
});
// アクセス: userResource.value(), userResource.isLoading(), userResource.error()
effect 的用法¶
仅将 effect() 用于需要响应信号变更的副作用(日志记录、第三方 DOM 操作)。不要使用 effect 同步信号 — 请改用 computed 或 linkedSignal。对于渲染后的 DOM 操作,请使用 afterRenderEffect。
// 正しい: 副作用
effect(() => console.log('User changed:', this.user()));
// 誤り: 代わりに computed を使用
effect(() => { this.fullName.set(`${this.first()} ${this.last()}`); });
模板¶
请使用 v17+ 的块语法。在 @for 中务必指定 track:
@for (item of items(); track item.id) {
<app-item [item]="item" />
}
@if (isLoading()) {
<app-spinner />
} @else if (error()) {
<app-error [message]="error()" />
} @else {
<app-content [data]="data()" />
}
将模板中的逻辑限制为简单条件表达式,并移到组件方法或管道中。
表单¶
选择与项目现有方法相匹配的表单策略:
- Signal Forms(v21+):推荐用于 v21+ 的新项目。基于信号的表单状态。
- Reactive Forms:
FormBuilder+FormGroup+FormControl。最适合具有动态验证的复杂表单。 - Template-Driven Forms:
ngModel。仅适用于简单表单。
// Reactive Forms — ほとんどのアプリの標準的なアプローチ
export class LoginComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]],
});
submit() {
if (this.form.valid) {
// this.form.value を使用
}
}
}
组件样式¶
使用 ViewEncapsulation.Emulated(默认)进行组件级样式。除非有意构建会泄漏样式的设计系统,否则请避免使用 ViewEncapsulation.None。
- 将样式限定在组件内,不要在组件样式表中使用全局类名
- 使用
:host为宿主元素设置样式 - 对于可主题化的值,优先使用 CSS 自定义属性
变更检测¶
- 在所有新组件中默认使用
ChangeDetectionStrategy.OnPush - 信号和
async管道会自动处理检测 — 避免使用markForCheck()和detectChanges() - 使用 OnPush 时,不要就地修改
@Input()对象
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 ev/ECC