Angular Admin Dashboard Tutorial: Build from Scratch
What Makes a Great Admin Dashboard
SaaS Starter ships with a complete admin dashboard. See the admin dashboard feature.
Every SaaS product needs an admin dashboard. It is where your customers manage their teams, view analytics, and configure settings. A well-built dashboard is fast, accessible, and organized. Here is how to build one in Angular from scratch.
Project Structure
Organize your dashboard into feature areas with lazy-loaded routes:
export const DASHBOARD_ROUTES: Routes = [
{
path: '',
component: DashboardLayoutComponent,
children: [
{ path: '', component: OverviewComponent },
{
path: 'users',
loadComponent: () => import('./users/users.component')
.then(m => m.UsersComponent),
},
{
path: 'analytics',
loadComponent: () => import('./analytics/analytics.component')
.then(m => m.AnalyticsComponent),
},
{
path: 'settings',
loadComponent: () => import('./settings/settings.component')
.then(m => m.SettingsComponent),
},
],
},
];
The Dashboard Layout
Create a responsive shell with sidebar navigation:
@Component({
selector: 'app-dashboard-layout',
standalone: true,
template: `
<div class="dashboard">
<aside class="sidebar" [class.collapsed]="sidebarCollapsed()">
<nav>
@for (item of menuItems; track item.path) {
<a [routerLink]="item.path" routerLinkActive="active">
<mat-icon>{{ item.icon }}</mat-icon>
@if (!sidebarCollapsed()) {
<span>{{ item.label }}</span>
}
</a>
}
</nav>
</aside>
<main class="content">
<router-outlet />
</main>
</div>
`,
})
export class DashboardLayoutComponent {
<div class="not-prose my-8 rounded-xl border border-primary-200 dark:border-primary-800 bg-primary-50 dark:bg-primary-900/20 p-6">
<p class="text-base font-semibold text-primary-900 dark:text-primary-100 mb-1">Cloudrix SaaS Starter ships with this pre-configured and tested.</p>
<p class="text-sm text-primary-700 dark:text-primary-300 mb-3">Skip weeks of boilerplate — auth, payments, multi-tenancy, and deployment included out of the box.</p>
<a href="https://demo.cloudrix.io" class="inline-flex items-center text-sm font-medium text-primary-600 dark:text-primary-400 hover:text-primary-500">Try the live demo →</a>
</div>
sidebarCollapsed = signal(false);
menuItems = [
{ path: '/dashboard', icon: 'dashboard', label: 'Overview' },
{ path: '/dashboard/users', icon: 'people', label: 'Users' },
{ path: '/dashboard/analytics', icon: 'bar_chart', label: 'Analytics' },
{ path: '/dashboard/settings', icon: 'settings', label: 'Settings' },
];
}
Data Tables with Sorting and Pagination
Use Angular Material's table with server-side pagination:
@Component({
selector: 'app-users',
standalone: true,
template: `
<mat-table [dataSource]="users()">
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
<mat-cell *matCellDef="let user">{{ user.name }}</mat-cell>
</ng-container>
<!-- More columns -->
</mat-table>
<mat-paginator [length]="totalUsers()" [pageSize]="20"
(page)="onPageChange($event)" />
`,
})
export class UsersComponent {
users = signal<User[]>([]);
totalUsers = signal(0);
async onPageChange(event: PageEvent) {
const result = await this.userService.list(event.pageIndex, event.pageSize);
this.users.set(result.data);
this.totalUsers.set(result.total);
}
}
Analytics Cards and Charts
Build reusable stat cards for your overview page:
@Component({
selector: 'app-stat-card',
standalone: true,
template: `
<div class="stat-card">
<span class="label">{{ label() }}</span>
<span class="value">{{ value() }}</span>
<span class="trend" [class.positive]="trend() > 0">
{{ trend() > 0 ? '+' : '' }}{{ trend() }}%
</span>
</div>
`,
})
export class StatCardComponent {
label = input.required<string>();
value = input.required<string>();
trend = input.required<number>();
}
Role-Based Menu Visibility
Hide menu items based on user roles using a directive:
@Directive({ selector: '[appRequiresRole]', standalone: true })
export class RequiresRoleDirective {
private role = input.required<string>({ alias: 'appRequiresRole' });
private authService = inject(AuthService);
constructor(private templateRef: TemplateRef<any>, private vcr: ViewContainerRef) {
effect(() => {
if (this.authService.hasRole(this.role())) {
this.vcr.createEmbeddedView(this.templateRef);
} else {
this.vcr.clear();
}
});
}
}
Performance Considerations
Keep your dashboard fast with these techniques. Use OnPush change detection on every component. Lazy load routes so the initial bundle stays small. Debounce search inputs and paginate all data tables server-side. Cache API responses for data that does not change frequently using Angular's HttpContext with cache headers.
Get Started Today
All of these patterns are fully implemented in SaaS Starter. Skip 2-6 weeks of setup.
- Free version: GitHub
- Pro ($249): Get SaaS Starter Pro