【研发类-前端开发Skills】angular-ui-patterns 技能

现代Angular UI模式,用于加载状态、错误处理和数据展示。适用于构建UI组件、处理异步数据或管理组件状态。

技能概述

angular-ui-patterns 技能提供现代Angular UI模式,专注于加载状态、错误处理和数据展示。该技能帮助开发者构建用户友好的界面,正确处理异步操作和错误情况。

下载地址:https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/angular-ui-patterns

核心原则

  • 永不显示过时UI:仅在真正加载时显示加载状态
  • 始终展示错误:用户必须知道何时出现问题
  • 乐观更新:让UI感觉即时响应
  • 渐进式披露:使用@defer在内容可用时显示
  • 优雅降级:部分数据优于无数据

加载状态模式

黄金法则

仅当没有数据可显示时才显示加载指示器。

@Component({
template: `
@if (error()) {
<app-error-state [error]="error()" (retry)="load()" />
} @else if (loading() && !items().length) {
<app-skeleton-list />
} @else if (!items().length) {
<app-empty-state message="No items found" />
} @else {
<app-item-list [items]="items()" />
}
`,
})
export class ItemListComponent {
private store = inject(ItemStore);
items = this.store.items;
loading = this.store.loading;
error = this.store.error;
}

加载状态决策树

决策流程

1. 是否有错误?→ 是:显示错误状态并提供重试选项

2. 是否正在加载且没有数据?→ 是:显示加载指示器(骨架屏/旋转器)

3. 是否有数据?→ 有数据:显示数据;空数据:显示空状态

骨架屏 vs 旋转器

使用骨架屏场景使用旋转器场景
已知内容形状未知内容形状
列表/卡片布局模态操作
初始页面加载按钮提交
内容占位符内联操作

控制流模式

@if/@else条件渲染

@if (user(); as user) {
<span>Welcome, {{ user.name }}</span>
} @else if (loading()) {
<app-spinner size="small" />
} @else {
<a routerLink="/login">Sign In</a>
}

@for与track

@for (item of items(); track item.id) {
<app-item-card [item]="item" (delete)="remove(item.id)" />
} @empty {
<app-empty-state icon="inbox" message="No items yet" actionLabel="Create Item" (action)="create()" />
}

@defer渐进式加载

<!-- 关键内容立即加载 -->
<app-header />
<app-hero-section />

<!-- 非关键内容延迟加载 -->
@defer (on viewport) {
<app-comments [postId]="postId()" />
} @placeholder {
<div class="h-32 bg-gray-100 animate-pulse"></div>
} @loading (minimum 200ms) {
<app-spinner />
} @error {
<app-error-state message="Failed to load comments" />
}

错误处理模式

错误处理层次

  • 内联错误(字段级):表单验证错误
  • Toast通知:可恢复错误,用户可重试
  • 错误横幅:页面级错误,数据仍部分可用
  • 全屏错误:不可恢复,需要用户操作

始终显示错误

// 正确 - 错误始终向用户显示
@Component({...})
export class CreateItemComponent {
private store = inject(ItemStore);
private toast = inject(ToastService);

async create(data: CreateItemDto) {
try {
await this.store.create(data);
this.toast.success('Item created successfully');
this.router.navigate(['/items']);
} catch (error) {
console.error('createItem failed:', error);
this.toast.error('Failed to create item. Please try again.');
}
}
}

// 错误 - 错误被静默捕获
async create(data: CreateItemDto) {
try {
await this.store.create(data);
} catch (error) {
console.error(error); // 用户看不到任何信息!
}
}

按钮状态模式

按钮加载状态

<button (click)="handleSubmit()" [disabled]="isSubmitting() || !form.valid" class="btn-primary">
@if (isSubmitting()) {
<app-spinner size="small" class="mr-2" />
Saving...
} @else {
Save Changes
}
</button>

空状态

空状态要求

每个列表/集合都必须有空状态:

@for (item of items(); track item.id) {
<app-item-card [item]="item" />
} @empty {
<app-empty-state icon="folder-open" title="No items yet" description="Create your first item to get started" actionLabel="Create Item" (action)="openCreateDialog()" />
}

最佳实践

  1. 加载状态:仅在无数据时显示加载指示器
  2. 错误显示:永不静默吞掉错误,始终向用户展示
  3. 按钮禁用:异步操作期间始终禁用触发器
  4. 空状态:每个列表都应有友好的空状态
  5. 渐进式加载:使用@defer延迟非关键内容
  6. 骨架屏:已知形状时优先使用骨架屏
  7. 乐观更新:让UI感觉即时响应

注意事项

限制:

  • 仅在任务明确匹配上述范围时使用此技能
  • 不要将输出视为环境特定验证、测试或专家审查的替代品
  • 如果缺少必需的输入、权限、安全边界或成功标准,请停止并请求澄清