AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Angular Ui Patterns

skill-agent-skills-hub-agent-skills-hub-angular-ui-patterns · by agent-skills-hub

Modern Angular UI patterns for loading states, error handling, and data display. Use when building UI components, handling async data, or managing component states.

No reviews yet
0 installs
8 views
0.0% view→install

Install

$ agentstack add skill-agent-skills-hub-agent-skills-hub-angular-ui-patterns

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-agent-skills-hub-agent-skills-hub-angular-ui-patterns)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Angular Ui Patterns? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Angular UI Patterns

Core Principles

  1. Never show stale UI - Loading states only when actually loading
  2. Always surface errors - Users must know when something fails
  3. Optimistic updates - Make the UI feel instant
  4. Progressive disclosure - Use @defer to show content as available
  5. Graceful degradation - Partial data is better than no data

Loading State Patterns

The Golden Rule

Show loading indicator ONLY when there's no data to display.

@Component({
  template: `
    @if (error()) {
      
    } @else if (loading() && !items().length) {
      
    } @else if (!items().length) {
      
    } @else {
      
    }
  `,
})
export class ItemListComponent {
  private store = inject(ItemStore);

  items = this.store.items;
  loading = this.store.loading;
  error = this.store.error;
}

Loading State Decision Tree

Is there an error?
  → Yes: Show error state with retry option
  → No: Continue

Is it loading AND we have no data?
  → Yes: Show loading indicator (spinner/skeleton)
  → No: Continue

Do we have data?
  → Yes, with items: Show the data
  → Yes, but empty: Show empty state
  → No: Show loading (fallback)

Skeleton vs Spinner

| Use Skeleton When | Use Spinner When | | -------------------- | --------------------- | | Known content shape | Unknown content shape | | List/card layouts | Modal actions | | Initial page load | Button submissions | | Content placeholders | Inline operations |


Control Flow Patterns

@if/@else for Conditional Rendering

@if (user(); as user) {
Welcome, {{ user.name }}
} @else if (loading()) {

} @else {
Sign In
}

@for with Track

@for (item of items(); track item.id) {

} @empty {

}

@defer for Progressive Loading


@defer (on viewport) {

} @placeholder {

} @loading (minimum 200ms) {

} @error {

}

Error Handling Patterns

Error Handling Hierarchy

1. Inline error (field-level) → Form validation errors
2. Toast notification → Recoverable errors, user can retry
3. Error banner → Page-level errors, data still partially usable
4. Full error screen → Unrecoverable, needs user action

Always Show Errors

CRITICAL: Never swallow errors silently.

// CORRECT - Error always surfaced to user
@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.');
    }
  }
}

// WRONG - Error silently caught
async create(data: CreateItemDto) {
  try {
    await this.store.create(data);
  } catch (error) {
    console.error(error); // User sees nothing!
  }
}

Error State Component Pattern

@Component({
  selector: "app-error-state",
  standalone: true,
  imports: [NgOptimizedImage],
  template: `
    
      
      {{ title() }}
      {{ message() }}
      @if (retry.observed) {
        Try Again
      }
    
  `,
})
export class ErrorStateComponent {
  title = input("Something went wrong");
  message = input("An unexpected error occurred");
  retry = output();
}

Button State Patterns

Button Loading State


  @if (isSubmitting()) {
  
  Saving... } @else { Save Changes }

Disable During Operations

CRITICAL: Always disable triggers during async operations.

// CORRECT - Button disabled while loading
@Component({
  template: `
    
      @if (saving()) {
         Saving...
      } @else {
        Save
      }
    
  `
})
export class SaveButtonComponent {
  saving = signal(false);

  async save() {
    this.saving.set(true);
    try {
      await this.service.save();
    } finally {
      this.saving.set(false);
    }
  }
}

// WRONG - User can click multiple times

  {{ saving() ? 'Saving...' : 'Save' }}

Empty States

Empty State Requirements

Every list/collection MUST have an empty state:

@for (item of items(); track item.id) {

} @empty {

}

Contextual Empty States

@Component({
  selector: "app-empty-state",
  template: `
    
      
      {{ title() }}
      {{ description() }}
      @if (actionLabel()) {
        
          {{ actionLabel() }}
        
      }
    
  `,
})
export class EmptyStateComponent {
  icon = input("inbox");
  title = input.required();
  description = input("");
  actionLabel = input(null);
  action = output();
}

Form Patterns

Form with Loading and Validation

@Component({
  template: `
    
      
        Name
        
        @if (isFieldInvalid("name")) {
          
            {{ getFieldError("name") }}
          
        }
      

      
        Email
        
        @if (isFieldInvalid("email")) {
          
            {{ getFieldError("email") }}
          
        }
      

      
        @if (submitting()) {
           Submitting...
        } @else {
          Submit
        }
      
    
  `,
})
export class UserFormComponent {
  private fb = inject(FormBuilder);

  submitting = signal(false);

  form = this.fb.group({
    name: ["", [Validators.required, Validators.minLength(2)]],
    email: ["", [Validators.required, Validators.email]],
  });

  isFieldInvalid(field: string): boolean {
    const control = this.form.get(field);
    return control ? control.invalid && control.touched : false;
  }

  getFieldError(field: string): string {
    const control = this.form.get(field);
    if (control?.hasError("required")) return "This field is required";
    if (control?.hasError("email")) return "Invalid email format";
    if (control?.hasError("minlength")) return "Too short";
    return "";
  }

  async onSubmit() {
    if (this.form.invalid) return;

    this.submitting.set(true);
    try {
      await this.service.submit(this.form.value);
      this.toast.success("Submitted successfully");
    } catch {
      this.toast.error("Submission failed");
    } finally {
      this.submitting.set(false);
    }
  }
}

Dialog/Modal Patterns

Confirmation Dialog

// dialog.service.ts
@Injectable({ providedIn: 'root' })
export class DialogService {
  private dialog = inject(Dialog); // CDK Dialog or custom

  async confirm(options: {
    title: string;
    message: string;
    confirmText?: string;
    cancelText?: string;
  }): Promise {
    const dialogRef = this.dialog.open(ConfirmDialogComponent, {
      data: options,
    });

    return await firstValueFrom(dialogRef.closed) ?? false;
  }
}

// Usage
async deleteItem(item: Item) {
  const confirmed = await this.dialog.confirm({
    title: 'Delete Item',
    message: `Are you sure you want to delete "${item.name}"?`,
    confirmText: 'Delete',
  });

  if (confirmed) {
    await this.store.delete(item.id);
  }
}

Anti-Patterns

Loading States

// WRONG - Spinner when data exists (causes flash on refetch)
@if (loading()) {
  
}

// CORRECT - Only show loading without data
@if (loading() && !items().length) {
  
}

Error Handling

// WRONG - Error swallowed
try {
  await this.service.save();
} catch (e) {
  console.log(e); // User has no idea!
}

// CORRECT - Error surfaced
try {
  await this.service.save();
} catch (e) {
  console.error("Save failed:", e);
  this.toast.error("Failed to save. Please try again.");
}

Button States


Submit

  @if (loading()) {
  
  } Submit

UI State Checklist

Before completing any UI component:

UI States

  • [ ] Error state handled and shown to user
  • [ ] Loading state shown only when no data exists
  • [ ] Empty state provided for collections (@empty block)
  • [ ] Buttons disabled during async operations
  • [ ] Buttons show loading indicator when appropriate

Data & Mutations

  • [ ] All async operations have error handling
  • [ ] All user actions have feedback (toast/visual)
  • [ ] Optimistic updates rollback on failure

Accessibility

  • [ ] Loading states announced to screen readers
  • [ ] Error messages linked to form fields
  • [ ] Focus management after state changes

Integration with Other Skills

  • angular-state-management: Use Signal stores for state
  • angular: Apply modern patterns (Signals, @defer)
  • testing-patterns: Test all UI states

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.