{"id":"7867718e-97e9-426c-bfd5-2e11a2572f34","shortId":"h4FFpa","kind":"skill","title":"angular-ui-patterns","tagline":"Modern Angular UI patterns for loading states, error handling, and data display. Use when building UI components, handling async data, or managing component states.","description":"# Angular UI Patterns\n\n## Core Principles\n\n1. **Never show stale UI** - Loading states only when actually loading\n2. **Always surface errors** - Users must know when something fails\n3. **Optimistic updates** - Make the UI feel instant\n4. **Progressive disclosure** - Use `@defer` to show content as available\n5. **Graceful degradation** - Partial data is better than no data\n\n---\n\n## Loading State Patterns\n\n### The Golden Rule\n\n**Show loading indicator ONLY when there's no data to display.**\n\n```typescript\n@Component({\n  template: `\n    @if (error()) {\n      <app-error-state [error]=\"error()\" (retry)=\"load()\" />\n    } @else if (loading() && !items().length) {\n      <app-skeleton-list />\n    } @else if (!items().length) {\n      <app-empty-state message=\"No items found\" />\n    } @else {\n      <app-item-list [items]=\"items()\" />\n    }\n  `,\n})\nexport class ItemListComponent {\n  private store = inject(ItemStore);\n\n  items = this.store.items;\n  loading = this.store.loading;\n  error = this.store.error;\n}\n```\n\n### Loading State Decision Tree\n\n```\nIs there an error?\n  → Yes: Show error state with retry option\n  → No: Continue\n\nIs it loading AND we have no data?\n  → Yes: Show loading indicator (spinner/skeleton)\n  → No: Continue\n\nDo we have data?\n  → Yes, with items: Show the data\n  → Yes, but empty: Show empty state\n  → No: Show loading (fallback)\n```\n\n### Skeleton vs Spinner\n\n| Use Skeleton When    | Use Spinner When      |\n| -------------------- | --------------------- |\n| Known content shape  | Unknown content shape |\n| List/card layouts    | Modal actions         |\n| Initial page load    | Button submissions    |\n| Content placeholders | Inline operations     |\n\n---\n\n## Control Flow Patterns\n\n### @if/@else for Conditional Rendering\n\n```html\n@if (user(); as user) {\n<span>Welcome, {{ user.name }}</span>\n} @else if (loading()) {\n<app-spinner size=\"small\" />\n} @else {\n<a routerLink=\"/login\">Sign In</a>\n}\n```\n\n### @for with Track\n\n```html\n@for (item of items(); track item.id) {\n<app-item-card [item]=\"item\" (delete)=\"remove(item.id)\" />\n} @empty {\n<app-empty-state\n  icon=\"inbox\"\n  message=\"No items yet\"\n  actionLabel=\"Create Item\"\n  (action)=\"create()\"\n/>\n}\n```\n\n### @defer for Progressive Loading\n\n```html\n<!-- Critical content loads immediately -->\n<app-header />\n<app-hero-section />\n\n<!-- Non-critical content deferred -->\n@defer (on viewport) {\n<app-comments [postId]=\"postId()\" />\n} @placeholder {\n<div class=\"h-32 bg-gray-100 animate-pulse\"></div>\n} @loading (minimum 200ms) {\n<app-spinner />\n} @error {\n<app-error-state message=\"Failed to load comments\" />\n}\n```\n\n---\n\n## Error Handling Patterns\n\n### Error Handling Hierarchy\n\n```\n1. Inline error (field-level) → Form validation errors\n2. Toast notification → Recoverable errors, user can retry\n3. Error banner → Page-level errors, data still partially usable\n4. Full error screen → Unrecoverable, needs user action\n```\n\n### Always Show Errors\n\n**CRITICAL: Never swallow errors silently.**\n\n```typescript\n// CORRECT - Error always surfaced to user\n@Component({...})\nexport class CreateItemComponent {\n  private store = inject(ItemStore);\n  private toast = inject(ToastService);\n\n  async create(data: CreateItemDto) {\n    try {\n      await this.store.create(data);\n      this.toast.success('Item created successfully');\n      this.router.navigate(['/items']);\n    } catch (error) {\n      console.error('createItem failed:', error);\n      this.toast.error('Failed to create item. Please try again.');\n    }\n  }\n}\n\n// WRONG - Error silently caught\nasync create(data: CreateItemDto) {\n  try {\n    await this.store.create(data);\n  } catch (error) {\n    console.error(error); // User sees nothing!\n  }\n}\n```\n\n### Error State Component Pattern\n\n```typescript\n@Component({\n  selector: \"app-error-state\",\n  standalone: true,\n  imports: [NgOptimizedImage],\n  template: `\n    <div class=\"error-state\">\n      <img ngSrc=\"/assets/error-icon.svg\" width=\"64\" height=\"64\" alt=\"\" />\n      <h3>{{ title() }}</h3>\n      <p>{{ message() }}</p>\n      @if (retry.observed) {\n        <button (click)=\"retry.emit()\" class=\"btn-primary\">Try Again</button>\n      }\n    </div>\n  `,\n})\nexport class ErrorStateComponent {\n  title = input(\"Something went wrong\");\n  message = input(\"An unexpected error occurred\");\n  retry = output<void>();\n}\n```\n\n---\n\n## Button State Patterns\n\n### Button Loading State\n\n```html\n<button\n  (click)=\"handleSubmit()\"\n  [disabled]=\"isSubmitting() || !form.valid\"\n  class=\"btn-primary\"\n>\n  @if (isSubmitting()) {\n  <app-spinner size=\"small\" class=\"mr-2\" />\n  Saving... } @else { Save Changes }\n</button>\n```\n\n### Disable During Operations\n\n**CRITICAL: Always disable triggers during async operations.**\n\n```typescript\n// CORRECT - Button disabled while loading\n@Component({\n  template: `\n    <button\n      [disabled]=\"saving()\"\n      (click)=\"save()\"\n    >\n      @if (saving()) {\n        <app-spinner size=\"sm\" /> Saving...\n      } @else {\n        Save\n      }\n    </button>\n  `\n})\nexport class SaveButtonComponent {\n  saving = signal(false);\n\n  async save() {\n    this.saving.set(true);\n    try {\n      await this.service.save();\n    } finally {\n      this.saving.set(false);\n    }\n  }\n}\n\n// WRONG - User can click multiple times\n<button (click)=\"save()\">\n  {{ saving() ? 'Saving...' : 'Save' }}\n</button>\n```\n\n---\n\n## Empty States\n\n### Empty State Requirements\n\nEvery list/collection MUST have an empty state:\n\n```html\n@for (item of items(); track item.id) {\n<app-item-card [item]=\"item\" />\n} @empty {\n<app-empty-state\n  icon=\"folder-open\"\n  title=\"No items yet\"\n  description=\"Create your first item to get started\"\n  actionLabel=\"Create Item\"\n  (action)=\"openCreateDialog()\"\n/>\n}\n```\n\n### Contextual Empty States\n\n```typescript\n@Component({\n  selector: \"app-empty-state\",\n  template: `\n    <div class=\"empty-state\">\n      <span class=\"icon\" [class]=\"icon()\"></span>\n      <h3>{{ title() }}</h3>\n      <p>{{ description() }}</p>\n      @if (actionLabel()) {\n        <button (click)=\"action.emit()\" class=\"btn-primary\">\n          {{ actionLabel() }}\n        </button>\n      }\n    </div>\n  `,\n})\nexport class EmptyStateComponent {\n  icon = input(\"inbox\");\n  title = input.required<string>();\n  description = input(\"\");\n  actionLabel = input<string | null>(null);\n  action = output<void>();\n}\n```\n\n---\n\n## Form Patterns\n\n### Form with Loading and Validation\n\n```typescript\n@Component({\n  template: `\n    <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n      <div class=\"form-field\">\n        <label for=\"name\">Name</label>\n        <input\n          id=\"name\"\n          formControlName=\"name\"\n          [class.error]=\"isFieldInvalid('name')\"\n        />\n        @if (isFieldInvalid(\"name\")) {\n          <span class=\"error-text\">\n            {{ getFieldError(\"name\") }}\n          </span>\n        }\n      </div>\n\n      <div class=\"form-field\">\n        <label for=\"email\">Email</label>\n        <input id=\"email\" type=\"email\" formControlName=\"email\" />\n        @if (isFieldInvalid(\"email\")) {\n          <span class=\"error-text\">\n            {{ getFieldError(\"email\") }}\n          </span>\n        }\n      </div>\n\n      <button type=\"submit\" [disabled]=\"form.invalid || submitting()\">\n        @if (submitting()) {\n          <app-spinner size=\"sm\" /> Submitting...\n        } @else {\n          Submit\n        }\n      </button>\n    </form>\n  `,\n})\nexport class UserFormComponent {\n  private fb = inject(FormBuilder);\n\n  submitting = signal(false);\n\n  form = this.fb.group({\n    name: [\"\", [Validators.required, Validators.minLength(2)]],\n    email: [\"\", [Validators.required, Validators.email]],\n  });\n\n  isFieldInvalid(field: string): boolean {\n    const control = this.form.get(field);\n    return control ? control.invalid && control.touched : false;\n  }\n\n  getFieldError(field: string): string {\n    const control = this.form.get(field);\n    if (control?.hasError(\"required\")) return \"This field is required\";\n    if (control?.hasError(\"email\")) return \"Invalid email format\";\n    if (control?.hasError(\"minlength\")) return \"Too short\";\n    return \"\";\n  }\n\n  async onSubmit() {\n    if (this.form.invalid) return;\n\n    this.submitting.set(true);\n    try {\n      await this.service.submit(this.form.value);\n      this.toast.success(\"Submitted successfully\");\n    } catch {\n      this.toast.error(\"Submission failed\");\n    } finally {\n      this.submitting.set(false);\n    }\n  }\n}\n```\n\n---\n\n## Dialog/Modal Patterns\n\n### Confirmation Dialog\n\n```typescript\n// dialog.service.ts\n@Injectable({ providedIn: 'root' })\nexport class DialogService {\n  private dialog = inject(Dialog); // CDK Dialog or custom\n\n  async confirm(options: {\n    title: string;\n    message: string;\n    confirmText?: string;\n    cancelText?: string;\n  }): Promise<boolean> {\n    const dialogRef = this.dialog.open(ConfirmDialogComponent, {\n      data: options,\n    });\n\n    return await firstValueFrom(dialogRef.closed) ?? false;\n  }\n}\n\n// Usage\nasync deleteItem(item: Item) {\n  const confirmed = await this.dialog.confirm({\n    title: 'Delete Item',\n    message: `Are you sure you want to delete \"${item.name}\"?`,\n    confirmText: 'Delete',\n  });\n\n  if (confirmed) {\n    await this.store.delete(item.id);\n  }\n}\n```\n\n---\n\n## Anti-Patterns\n\n### Loading States\n\n```typescript\n// WRONG - Spinner when data exists (causes flash on refetch)\n@if (loading()) {\n  <app-spinner />\n}\n\n// CORRECT - Only show loading without data\n@if (loading() && !items().length) {\n  <app-spinner />\n}\n```\n\n### Error Handling\n\n```typescript\n// WRONG - Error swallowed\ntry {\n  await this.service.save();\n} catch (e) {\n  console.log(e); // User has no idea!\n}\n\n// CORRECT - Error surfaced\ntry {\n  await this.service.save();\n} catch (e) {\n  console.error(\"Save failed:\", e);\n  this.toast.error(\"Failed to save. Please try again.\");\n}\n```\n\n### Button States\n\n```html\n<!-- WRONG - Button not disabled during submission -->\n<button (click)=\"submit()\">Submit</button>\n\n<!-- CORRECT - Disabled and shows loading -->\n<button (click)=\"submit()\" [disabled]=\"loading()\">\n  @if (loading()) {\n  <app-spinner size=\"sm\" />\n  } Submit\n</button>\n```\n\n---\n\n## UI State Checklist\n\nBefore completing any UI component:\n\n### UI States\n\n- [ ] Error state handled and shown to user\n- [ ] Loading state shown only when no data exists\n- [ ] Empty state provided for collections (`@empty` block)\n- [ ] Buttons disabled during async operations\n- [ ] Buttons show loading indicator when appropriate\n\n### Data & Mutations\n\n- [ ] All async operations have error handling\n- [ ] All user actions have feedback (toast/visual)\n- [ ] Optimistic updates rollback on failure\n\n### Accessibility\n\n- [ ] Loading states announced to screen readers\n- [ ] Error messages linked to form fields\n- [ ] Focus management after state changes\n\n---\n\n## Integration with Other Skills\n\n- **angular-state-management**: Use Signal stores for state\n- **angular**: Apply modern patterns (Signals, @defer)\n- **testing-patterns**: Test all UI states\n\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.","tags":["angular","patterns","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows"],"capabilities":["skill","source-sickn33","skill-angular-ui-patterns","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/angular-ui-patterns","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 34964 github stars · SKILL.md body (11,323 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-25T00:50:26.115Z","embedding":null,"createdAt":"2026-04-18T21:30:59.717Z","updatedAt":"2026-04-25T00:50:26.115Z","lastSeenAt":"2026-04-25T00:50:26.115Z","tsv":"'/items':378 '1':34,302 '2':45,311,693 '200ms':294 '3':55,319 '4':63,330 '5':73 'access':975 'action':212,276,337,585,630,966,1031 'action.emit':609 'actionlabel':273,582,606,614,625 'actual':43 'alway':46,338,349,484 'angular':2,6,29,998,1006 'angular-state-manag':997 'angular-ui-pattern':1 'announc':978 'anti':836 'anti-pattern':835 'app':106,124,254,264,287,420,556,563,594 'app-com':286 'app-empty-st':263,562,593 'app-error-st':105,419 'app-item-card':253,555 'app-item-list':123 'appli':1007 'applic':1025 'appropri':955 'ask':1069 'async':23,365,397,488,514,743,784,808,948,959 'avail':72 'await':370,402,519,751,803,814,832,869,883 'banner':321 'better':79 'block':944 'boolean':700 'boundari':1077 'btn':437,472,612 'btn-primari':436,471,611 'build':19 'button':216,432,457,460,464,492,498,530,607,667,898,901,905,945,950 'canceltext':793 'card':256,558 'catch':379,405,757,871,885 'caught':396 'caus':846 'cdk':780 'chang':479,992 'checklist':915 'clarif':1071 'class':130,355,435,442,470,509,599,601,610,616,679,774 'class.error':653 'clear':1044 'click':433,465,501,527,531,608,902,906 'collect':942 'comment':288 'complet':917 'compon':21,27,101,353,414,417,496,591,640,920 'condit':228 'confirm':766,785,813,831 'confirmdialogcompon':799 'confirmtext':791,828 'console.error':381,407,887 'console.log':873 'const':701,714,796,812 'content':70,204,207,218 'contextu':587 'continu':158,173 'control':222,702,706,715,719,728,736 'control.invalid':707 'control.touched':708 'core':32 'correct':347,491,852,879 'creat':274,277,366,375,388,398,575,583 'createitem':382 'createitemcompon':356 'createitemdto':368,400 'criteria':1080 'critic':341,483 'custom':783 'data':15,24,77,82,97,166,177,183,326,367,372,399,404,800,844,857,936,956 'decis':144 'defer':67,278,283,1011 'degrad':75 'delet':259,817,826,829 'deleteitem':809 'describ':1032,1048 'descript':574,604,623 'dialog':767,777,779,781 'dialog.service.ts':769 'dialog/modal':764 'dialogref':797 'dialogref.closed':805 'dialogservic':775 'disabl':467,480,485,493,499,670,908,946 'disclosur':65 'display':16,99 'e':872,874,886,890 'els':113,118,122,226,237,240,477,506,676 'email':661,664,666,694,730,733 'empti':186,188,262,265,536,538,546,561,564,588,595,938,943 'emptystatecompon':617 'environ':1060 'environment-specif':1059 'error':12,48,104,107,109,110,140,149,152,295,296,299,304,310,315,320,325,332,340,344,348,380,384,394,406,408,412,421,453,862,866,880,923,962,982 'errorstatecompon':443 'everi':541 'execut':1027 'exist':845,937 'expert':1065 'export':129,354,441,508,615,678,773 'fail':54,383,386,760,889,892 'failur':974 'fallback':193 'fals':513,523,687,709,763,806 'fb':682 'feedback':968 'feel':61 'field':306,698,704,711,717,724,987 'field-level':305 'final':521,761 'first':577 'firstvaluefrom':804 'flash':847 'flow':223 'focus':988 'folder':568 'folder-open':567 'form':308,632,634,642,644,688,986 'form.invalid':671 'form.valid':469 'format':734 'formbuild':684 'formcontrolnam':651 'formgroup':643 'full':331 'get':580 'getfielderror':659,665,710 'golden':87 'grace':74 'handl':13,22,297,300,863,925,963 'handlesubmit':466 'haserror':720,729,737 'hierarchi':301 'html':230,246,282,463,548,900 'icon':267,566,600,602,618 'id':649 'idea':878 'import':425 'inbox':268,620 'indic':91,170,953 'initi':213 'inject':134,359,363,683,770,778 'inlin':220,303 'input':445,450,619,624,626,648,1074 'input.required':622 'instant':62 'integr':993 'invalid':732 'isfieldinvalid':654,657,663,697 'issubmit':468,475 'item':116,120,125,127,128,136,180,248,250,255,257,258,271,275,374,389,550,552,557,559,560,572,578,584,810,811,818,860 'item.id':252,261,554,834 'item.name':827 'itemlistcompon':131 'itemstor':135,360 'know':51 'known':203 'layout':210 'length':117,121,861 'level':307,324 'limit':1036 'link':984 'list':126 'list/card':209 'list/collection':542 'load':10,39,44,83,90,112,115,138,142,161,169,192,215,239,281,292,461,495,636,838,851,855,859,909,911,930,952,976 'make':58 'manag':26,989,1000 'match':1045 'messag':269,429,449,789,819,983 'minimum':293 'minlength':738 'miss':1082 'modal':211 'modern':5,1008 'multipl':528 'must':50,543 'mutat':957 'name':647,650,652,655,658,660,690 'need':335 'never':35,342 'ngoptimizedimag':426 'ngsubmit':645 'noth':411 'notif':313 'null':628,629 'occur':454 'onsubmit':646,744 'open':569 'opencreatedialog':586 'oper':221,482,489,949,960 'optimist':56,970 'option':156,786,801 'output':456,631,1054 'overview':1035 'page':214,323 'page-level':322 'partial':76,328 'pattern':4,8,31,85,224,298,415,459,633,765,837,1009,1014 'permiss':1075 'placehold':219,291 'pleas':390,895 'postid':289,290 'primari':438,473,613 'principl':33 'privat':132,357,361,681,776 'progress':64,280 'promis':795 'provid':940 'providedin':771 'reader':981 'recover':314 'refetch':849 'remov':260 'render':229 'requir':540,721,726,1073 'retri':111,155,318,455 'retry.emit':434 'retry.observed':431 'return':705,722,731,739,742,747,802 'review':1066 'rollback':972 'root':772 'rule':88 'safeti':1076 'save':476,478,500,502,504,505,507,511,515,532,533,534,535,888,894 'savebuttoncompon':510 'scope':1047 'screen':333,980 'see':410 'selector':418,592 'shape':205,208 'short':741 'show':36,69,89,151,168,181,187,191,339,854,951 'shown':927,932 'sign':241 'signal':512,686,1002,1010 'silent':345,395 'skeleton':194,198 'skill':996,1023,1039 'skill-angular-ui-patterns' 'someth':53,446 'source-sickn33' 'span':598 'specif':1061 'spinner':196,201,842 'spinner/skeleton':171 'stale':37 'standalon':423 'start':581 'state':11,28,40,84,108,143,153,189,266,413,422,458,462,537,539,547,565,589,596,839,899,914,922,924,931,939,977,991,999,1005,1018 'still':327 'stop':1067 'store':133,358,1003 'string':627,699,712,713,788,790,792,794 'submiss':217,759 'submit':669,672,674,675,677,685,755,903,904,907,912 'substitut':1057 'success':376,756,1079 'sure':822 'surfac':47,350,881 'swallow':343,867 'task':1043 'templat':102,427,497,597,641 'test':1013,1015,1063 'testing-pattern':1012 'this.dialog.confirm':815 'this.dialog.open':798 'this.fb.group':689 'this.form.get':703,716 'this.form.invalid':746 'this.form.value':753 'this.router.navigate':377 'this.saving.set':516,522 'this.service.save':520,870,884 'this.service.submit':752 'this.store.create':371,403 'this.store.delete':833 'this.store.error':141 'this.store.items':137 'this.store.loading':139 'this.submitting.set':748,762 'this.toast.error':385,758,891 'this.toast.success':373,754 'time':529 'titl':428,444,570,603,621,787,816 'toast':312,362 'toast/visual':969 'toastservic':364 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'track':245,251,553 'treat':1052 'tree':145 'tri':369,391,401,439,518,750,868,882,896 'trigger':486 'true':424,517,749 'type':668 'typescript':100,346,416,490,590,639,768,840,864 'ui':3,7,20,30,38,60,913,919,921,1017 'unexpect':452 'unknown':206 'unrecover':334 'updat':57,971 'usabl':329 'usag':807 'use':17,66,197,200,1001,1021,1037 'user':49,232,234,316,336,352,409,525,875,929,965 'user.name':236 'userformcompon':680 'valid':309,638,1062 'validators.email':696 'validators.minlength':692 'validators.required':691,695 'viewport':285 'vs':195 'want':824 'welcom':235 'went':447 'without':856 'workflow':1029 'wrong':393,448,524,841,865 'yes':150,167,178,184 'yet':272,573","prices":[{"id":"585b4adb-6972-4dd2-b5e5-ce8c3fa18fff","listingId":"7867718e-97e9-426c-bfd5-2e11a2572f34","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T21:30:59.717Z"}],"sources":[{"listingId":"7867718e-97e9-426c-bfd5-2e11a2572f34","source":"github","sourceId":"sickn33/antigravity-awesome-skills/angular-ui-patterns","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/angular-ui-patterns","isPrimary":false,"firstSeenAt":"2026-04-18T21:30:59.717Z","lastSeenAt":"2026-04-25T00:50:26.115Z"}],"details":{"listingId":"7867718e-97e9-426c-bfd5-2e11a2572f34","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"angular-ui-patterns","github":{"repo":"sickn33/antigravity-awesome-skills","stars":34964,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-04-24T06:41:17Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"5fdb0d970f05c6205e3efb57c2f7a8a9ef725875","skill_md_path":"skills/angular-ui-patterns/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/angular-ui-patterns"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"angular-ui-patterns","description":"Modern Angular UI patterns for loading states, error handling, and data display. Use when building UI components, handling async data, or managing component states."},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/angular-ui-patterns"},"updatedAt":"2026-04-25T00:50:26.115Z"}}