Skip to main content Skills Marketplace Descubra e explore skills de IA criadas pela comunidade.
Ocupações relacionadas SOC
Baseado na classificação ocupacional SOC
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Copiar promptMostrar detalhes do prompt Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
npx skills add https://github.com/BEKO2210/Firstbrain --skill angular-ui-patternsO comando permanece em uma só linha. Role horizontalmente para revisá-lo antes de copiar.
Prefere uma cópia local? Baixe os arquivos disponíveis atualmente no SkillsMP.
Baixar Zip Baixando... Explorador de arquivos
2 arquivos 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. type skill created 2026-02-27T00:00:00.000Z domain software-development category frontend risk safe source self tags ["skill","software-development","frontend","angular"]
Angular UI Patterns
Core Principles
Never show stale UI - Loading states only when actually loading
Always surface errors - Users must know when something fails
Optimistic updates - Make the UI feel instant
Progressive disclosure - Use @defer to show content as available
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()) {
<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 ;
}
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
Content placeholders Inline operations
Control Flow Patterns
@if/@else for Conditional Rendering @if (user(); as user) {
<span > Welcome, {{ user.name }}</span >
} @else if (loading()) {
<app-spinner size ="small" />
} @else {
<a routerLink ="/login" > Sign In</a >
}
@for with 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 for Progressive Loading
<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" />
}
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.
@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);
}
}
Error State Component Pattern @Component ({
selector : "app-error-state" ,
standalone : true ,
imports : [NgOptimizedImage ],
template : `
<div class="error-state">
<img ngSrc="/assets/error-icon.svg" width="64" height="64" alt="" />
<h3>{{ title() }}</h3>
<p>{{ message() }}</p>
@if (retry.observed) {
<button (click)="retry.emit()" class="btn-primary">Try Again</button>
}
</div>
` ,
})
export class ErrorStateComponent {
title = input ("Something went wrong" );
message = input ("An unexpected error occurred" );
retry = output<void >();
}
Button State Patterns
Button Loading State <button
(click )="handleSubmit()"
[disabled ]="isSubmitting() || !form.valid"
class ="btn-primary"
>
@if (isSubmitting()) {
<app-spinner size ="small" class ="mr-2" />
Saving... } @else { Save Changes }
</button >
Disable During Operations CRITICAL: Always disable triggers during async operations.
@Component ({
template : `
<button
[disabled]="saving()"
(click)="save()"
>
@if (saving()) {
<app-spinner size="sm" /> Saving...
} @else {
Save
}
</button>
`
})
export class SaveButtonComponent {
saving = signal (false );
async save ( ) {
this .saving .set (true );
try {
await this .service .save ();
} finally {
this .saving .set (false );
}
}
}
<button (click)="save()" >
{{ saving () ? 'Saving...' : 'Save' }}
</button>
Empty States
Empty State Requirements Every list/collection MUST have an empty state:
@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()"
/>
}
Contextual Empty States @Component ({
selector : "app-empty-state" ,
template : `
<div class="empty-state">
<span class="icon" [class]="icon()"></span>
<h3>{{ title() }}</h3>
<p>{{ description() }}</p>
@if (actionLabel()) {
<button (click)="action.emit()" class="btn-primary">
{{ actionLabel() }}
</button>
}
</div>
` ,
})
export class EmptyStateComponent {
icon = input ("inbox" );
title = input.required <string >();
description = input ("" );
actionLabel = input<string | null >(null );
action = output<void >();
}
Form Patterns
Form with Loading and Validation @Component ({
template : `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div class="form-field">
<label for="name">Name</label>
<input
id="name"
formControlName="name"
[class.error]="isFieldInvalid('name')"
/>
@if (isFieldInvalid("name")) {
<span class="error-text">
{{ getFieldError("name") }}
</span>
}
</div>
<div class="form-field">
<label for="email">Email</label>
<input id="email" type="email" formControlName="email" />
@if (isFieldInvalid("email")) {
<span class="error-text">
{{ getFieldError("email") }}
</span>
}
</div>
<button type="submit" [disabled]="form.invalid || submitting()">
@if (submitting()) {
<app-spinner size="sm" /> Submitting...
} @else {
Submit
}
</button>
</form>
` ,
})
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
@Injectable ({ providedIn : 'root' })
export class DialogService {
private dialog = inject (Dialog );
async confirm (options : {
title : string ;
message : string ;
confirmText ?: string ;
cancelText ?: string ;
}): Promise <boolean > {
const dialogRef = this .dialog .open (ConfirmDialogComponent , {
data : options,
});
return await firstValueFrom (dialogRef.closed ) ?? false ;
}
}
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
@if (loading ()) {
<app-spinner />
}
@if (loading () && !items ().length ) {
<app-spinner />
}
Error Handling
try {
await this .service .save ();
} catch (e) {
console .log (e);
}
try {
await this .service .save ();
} catch (e) {
console .error ("Save failed:" , e);
this .toast .error ("Failed to save. Please try again." );
}
Button States
<button (click )="submit()" > Submit</button >
<button (click )="submit()" [disabled ]="loading()" >
@if (loading()) {
<app-spinner size ="sm" />
} Submit
</button >
UI State Checklist Before completing any UI component:
UI States
Data & Mutations
Accessibility
Integration with Other Skills
angular-state-management : Use Signal stores for state
angular : Apply modern patterns (Signals, @defer)
testing-patterns : Test all UI states
When to Use This skill is applicable to execute the workflow or actions described in the overview.
Connections
Domain: [[Software Entwicklung]]
Kategorie: [[Frontend Entwicklung]]
Navigation: [[Skills Uebersicht]], [[Home]]