Skip to content
← Back to blog
AngularDashboardTutorialAdmin Panel

Angular Admin Dashboard Tutorial: Build from Scratch

Firas Sayah·May 23, 2026·5 min read

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 &rarr;</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.

F

Firas Sayah

Senior Software Engineer

Full-stack developer with 5+ years building production SaaS applications with NestJS and Angular.