Angular Enterprise Dashboard - Phase 3B.3: Six States of Data — ResourceStatus & the UI

Most applications handle two data states: “loading” and “loaded.” Maybe three if they remember errors. But in a real enterprise dashboard, there are six distinct states — and handling each explicitly is the difference between a polished product and a buggy one.
Every Async Operation Has Six Faces
In this post, we’ll build a UI that correctly responds to every ResourceStatus value.
1 📊 The Six States
Angular’s resource() exposes a status() signal with these possible values:
stateDiagram-v2
[*] --> idle: No params
idle --> loading: First fetch
loading --> resolved: Success
loading --> error: Failure
resolved --> reloading: reload() called
reloading --> resolved: Success
reloading --> error: Failure
resolved --> loading: params change
resolved --> local: .update() / .set()
local --> loading: params change
local --> reloading: reload() called
error --> loading: params change
error --> reloading: reload() called
| Status | Meaning | UI Treatment |
|---|---|---|
idle | No request pending (params returned undefined) | Welcome message |
loading | First fetch in progress | Shimmer skeleton |
reloading | Re-fetch after reload() — data still visible | Overlay indicator |
resolved | Data successfully loaded | Project cards |
error | Loader threw an error | Error banner + Retry |
local | Data mutated locally via .update() | Data + sync notice |
2 🏗️ The Template: @switch Over Status
Our template uses Angular’s @switch to render the appropriate UI for each state:
@switch (projectsResource.status()) { @case ('idle') {
<div class="empty-state">
<span class="empty-icon">🔍</span>
<p>Type a search query to find projects.</p>
</div>
} @case ('loading') {
<div class="skeleton-grid">
@for (i of skeletonItems; track i) {
<div class="skeleton-card"></div>
}
</div>
} @case ('error') {
<div class="error-banner">
<span>❌ {{ getErrorMessage(projectsResource.error()) }}</span>
<button (click)="handleReload()">Retry</button>
</div>
} @default {
<!-- resolved / reloading / local all show data -->
<div class="project-grid">
@for (project of projectsResource.value(); track project.id) {
<article class="project-card">...</article>
} @empty {
<div class="empty-state">📭 No projects match your search.</div>
}
</div>
} }Why @default? The resolved, reloading, and local states all have data to show. They differ in secondary treatment (overlay, notice), not in the core content. Grouping them under @default avoids duplication.
3 💀 Shimmer Skeletons: Loading Done Right
Instead of a spinner, we use shimmer skeleton cards that hint at the shape of the content:
.skeleton-card {
height: 200px;
border-radius: var(--radius-lg, 16px);
background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}Why skeletons over spinners? Skeletons reduce perceived loading time because they give the user a sense of the page’s structure. Studies show users rate skeleton-loaded pages as 10-15% faster than spinner-loaded pages with identical real load times.
4 🔄 The reload() Pattern
Sometimes the user wants fresh data. Our “Reload” button calls projectsResource.reload():
handleReload(): void {
this.projectsResource.reload();
}When reload() is called:
- Status transitions from
resolved→reloading - The existing data stays visible (unlike
loading, which clears it) - The loader runs again with the same params
- On success, status returns to
resolvedwith fresh data
We show a subtle overlay during reloading:
@if (projectsResource.status() === 'reloading') {
<div class="reload-overlay">Refreshing...</div>
}5 📛 The Status Badge
For educational purposes (and great UX), we show the current ResourceStatus as a colored pill:
<span class="status-badge" [class]="'status-' + projectsResource.status()">
{{ projectsResource.status() | uppercase }}
</span>Each status gets its own color:
.status-idle {
background: #f1f5f9;
color: #64748b;
}
.status-loading,
.status-reloading {
background: #ede9fe;
color: #7c3aed;
}
.status-resolved {
background: #ecfdf5;
color: #059669;
}
.status-error {
background: #fef2f2;
color: #dc2626;
}
.status-local {
background: #fef3c7;
color: #d97706;
}6 🎓 The Teaching Moment: Status-Driven UI as a Pattern
This pattern isn’t specific to Angular. Any async operation — in React, Vue, Svelte, or vanilla JS — goes through these states. The key principle is:
Never assume your data is “just there.” Every async operation has a lifecycle, and every state deserves explicit UI treatment.
The resource() API makes this easy because the status is a first-class signal. No more manual isLoading / hasError / data boolean juggling.
7 Coming Up Next
We’ve handled all six states, but there’s one more advanced pattern: optimistic updates. In Phase 3B.4, we’ll see how .update() lets you modify data locally without re-fetching — and how the 'local' status communicates this to the user.
Open your browser DevTools and watch the status badge change as you search, reload, and interact. It’s a real-time view of the resource lifecycle!