๐Ÿ””Welcome

HaloLight multi-framework admin dashboard docs is now live!

Supports 12+ framework versions. Welcome to try.

Skip to content

Angular Version โ€‹

HaloLight Angular version is built on Angular 21 with Signals + Standalone Components + TypeScript.

Live Preview: https://halolight-angular.h7ml.cn/

GitHub: https://github.com/halolight/halolight-angular

Tech Stack โ€‹

TechnologyVersionDescription
Angular21.xEnterprise framework
TypeScript5.xType safety
Tailwind CSS4.xAtomic CSS
Angular CDK21.xUI primitives library
spartan/uilatestUI component library (Radix-style)
TanStack Query5.xServer state
NgRx Signals21.xReactive state management
Zod3.xData validation
angular-gridster2latestDrag-and-drop layout
ngx-echartslatestChart visualization
Mock.js1.xData mocking

Directory Structure โ€‹

halolight-angular/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ app/
โ”‚   โ”‚   โ”œโ”€โ”€ pages/                  # Page components
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ admin/             # Admin pages
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ dashboard/     # Dashboard
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ users/         # User management
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ roles/         # Role management
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ permissions/   # Permission management
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ settings/      # System settings
โ”‚   โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ profile/       # User profile
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ auth/              # Auth pages
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ login/
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ register/
โ”‚   โ”‚   โ”‚       โ”œโ”€โ”€ forgot-password/
โ”‚   โ”‚   โ”‚       โ””โ”€โ”€ reset-password/
โ”‚   โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ ui/                # spartan/ui components
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ layout/            # Layout components
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ admin-layout/
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ auth-layout/
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ sidebar/
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ header/
โ”‚   โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ footer/
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ dashboard/         # Dashboard components
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ dashboard-grid/
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ widget-wrapper/
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ stats-widget/
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ chart-widget/
โ”‚   โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ shared/            # Shared components
โ”‚   โ”‚   โ”œโ”€โ”€ services/              # Service layer
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ api.service.ts
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ auth.service.ts
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ users.service.ts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ”‚   โ”œโ”€โ”€ stores/                # NgRx Signals Stores
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ auth.store.ts
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ ui-settings.store.ts
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ dashboard.store.ts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ tabs.store.ts
โ”‚   โ”‚   โ”œโ”€โ”€ guards/                # Route guards
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ auth.guard.ts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ permission.guard.ts
โ”‚   โ”‚   โ”œโ”€โ”€ interceptors/          # HTTP interceptors
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ auth.interceptor.ts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ error.interceptor.ts
โ”‚   โ”‚   โ”œโ”€โ”€ directives/            # Directives
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ permission.directive.ts
โ”‚   โ”‚   โ”œโ”€โ”€ pipes/                 # Pipes
โ”‚   โ”‚   โ”œโ”€โ”€ lib/                   # Utility library
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ utils.ts
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ cn.ts
โ”‚   โ”‚   โ”œโ”€โ”€ types/                 # Type definitions
โ”‚   โ”‚   โ”œโ”€โ”€ mocks/                 # Mock data
โ”‚   โ”‚   โ”œโ”€โ”€ app.routes.ts          # Route configuration
โ”‚   โ”‚   โ”œโ”€โ”€ app.config.ts          # App configuration
โ”‚   โ”‚   โ””โ”€โ”€ app.component.ts       # Root component
โ”‚   โ”œโ”€โ”€ environments/              # Environment configuration
โ”‚   โ””โ”€โ”€ styles.css                 # Global styles
โ”œโ”€โ”€ public/                        # Static assets
โ”œโ”€โ”€ angular.json
โ”œโ”€โ”€ tailwind.config.js
โ”œโ”€โ”€ tsconfig.json
โ””โ”€โ”€ package.json

Quick Start โ€‹

Installation โ€‹

bash
git clone https://github.com/halolight/halolight-angular.git
cd halolight-angular
pnpm install

Environment Variables โ€‹

bash
cp src/environments/environment.example.ts src/environments/environment.development.ts
ts
// src/environments/environment.development.ts
export const environment = {
  production: false,
  apiUrl: '/api',
  useMock: true,
  appTitle: 'Admin Pro',
  brandName: 'Halolight',
  demoEmail: 'admin@halolight.h7ml.cn',
  demoPassword: '123456',
  showDemoHint: true,
};

Start Development โ€‹

bash
pnpm start
# or
ng serve

Visit http://localhost:4200

Build for Production โ€‹

bash
pnpm build
# or
ng build --configuration production

Core Features โ€‹

State Management (NgRx Signals) โ€‹

ts
// stores/auth.store.ts
import { signalStore, withState, withMethods, withComputed, patchState } from '@ngrx/signals';
import { computed, inject } from '@angular/core';
import { AuthService } from '../services/auth.service';

interface AuthState {
  user: User | null;
  token: string | null;
  loading: boolean;
}

const initialState: AuthState = {
  user: null,
  token: null,
  loading: false,
};

export const AuthStore = signalStore(
  { providedIn: 'root' },
  withState(initialState),
  withComputed((store) => ({
    isAuthenticated: computed(() => !!store.token() && !!store.user()),
    permissions: computed(() => store.user()?.permissions ?? []),
  })),
  withMethods((store, authService = inject(AuthService)) => ({
    async login(credentials: LoginCredentials) {
      patchState(store, { loading: true });
      try {
        const response = await authService.login(credentials);
        patchState(store, {
          user: response.user,
          token: response.token,
          loading: false,
        });
      } catch (error) {
        patchState(store, { loading: false });
        throw error;
      }
    },

    logout() {
      patchState(store, { user: null, token: null });
    },

    hasPermission(permission: string): boolean {
      const permissions = store.permissions();
      return permissions.some(p =>
        p === '*' || p === permission ||
        (p.endsWith(':*') && permission.startsWith(p.slice(0, -1)))
      );
    },
  }))
);

Data Fetching (TanStack Query) โ€‹

ts
// services/users.service.ts
import { Injectable, inject } from '@angular/core';
import { injectQuery, injectMutation, injectQueryClient } from '@tanstack/angular-query-experimental';
import { ApiService } from './api.service';

@Injectable({ providedIn: 'root' })
export class UsersService {
  private api = inject(ApiService);
  private queryClient = injectQueryClient();

  getUsers(params?: UserQueryParams) {
    return injectQuery(() => ({
      queryKey: ['users', params],
      queryFn: () => this.api.get<UserListResponse>('/users', { params }),
    }));
  }

  createUser() {
    return injectMutation(() => ({
      mutationFn: (data: CreateUserDto) => this.api.post<User>('/users', data),
      onSuccess: () => {
        this.queryClient.invalidateQueries({ queryKey: ['users'] });
      },
    }));
  }
}

Permission Directive โ€‹

ts
// directives/permission.directive.ts
import { Directive, Input, TemplateRef, ViewContainerRef, inject, effect } from '@angular/core';
import { AuthStore } from '../stores/auth.store';

@Directive({
  selector: '[appPermission]',
  standalone: true,
})
export class PermissionDirective {
  private templateRef = inject(TemplateRef<unknown>);
  private viewContainer = inject(ViewContainerRef);
  private authStore = inject(AuthStore);

  @Input() set appPermission(permission: string) {
    effect(() => {
      const hasPermission = this.authStore.hasPermission(permission);
      this.viewContainer.clear();
      if (hasPermission) {
        this.viewContainer.createEmbeddedView(this.templateRef);
      }
    });
  }
}
html
<!-- Usage -->
<button *appPermission="'users:delete'">Delete</button>

Permission Component โ€‹

ts
// components/permission-guard.component.ts
import { Component, Input, inject, computed } from '@angular/core';
import { AuthStore } from '../../stores/auth.store';

@Component({
  selector: 'app-permission-guard',
  standalone: true,
  template: `
    @if (hasPermission()) {
      <ng-content />
    } @else {
      <ng-content select="[fallback]" />
    }
  `,
})
export class PermissionGuardComponent {
  @Input({ required: true }) permission!: string;

  private authStore = inject(AuthStore);
  hasPermission = computed(() => this.authStore.hasPermission(this.permission));
}
html
<!-- Usage -->
<app-permission-guard permission="users:delete">
  <app-delete-button />
  <span fallback>No permission</span>
</app-permission-guard>

Draggable Dashboard โ€‹

ts
// components/dashboard/dashboard-grid.component.ts
import { Component, inject, computed } from '@angular/core';
import { GridsterModule, GridsterConfig, GridsterItem } from 'angular-gridster2';
import { DashboardStore } from '../../stores/dashboard.store';

@Component({
  selector: 'app-dashboard-grid',
  standalone: true,
  imports: [GridsterModule, WidgetWrapperComponent],
  template: `
    <gridster [options]="options()">
      @for (widget of widgets(); track widget.id) {
        <gridster-item [item]="widget">
          <app-widget-wrapper [widget]="widget" />
        </gridster-item>
      }
    </gridster>
  `,
})
export class DashboardGridComponent {
  private dashboardStore = inject(DashboardStore);

  widgets = this.dashboardStore.widgets;
  isEditing = this.dashboardStore.isEditing;

  options = computed<GridsterConfig>(() => ({
    gridType: 'fit',
    displayGrid: this.isEditing() ? 'always' : 'none',
    draggable: { enabled: this.isEditing() },
    resizable: { enabled: this.isEditing() },
    pushItems: true,
    minCols: 12,
    maxCols: 12,
    minRows: 4,
    defaultItemCols: 3,
    defaultItemRows: 2,
    itemChangeCallback: (item) => this.dashboardStore.updateWidget(item),
  }));
}

Route Guards โ€‹

ts
// guards/auth.guard.ts
import { inject } from '@angular/core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthStore } from '../stores/auth.store';

export const authGuard: CanActivateFn = (route, state) => {
  const authStore = inject(AuthStore);
  const router = inject(Router);

  if (!authStore.isAuthenticated()) {
    router.navigate(['/login'], { queryParams: { redirect: state.url } });
    return false;
  }

  return true;
};

// guards/permission.guard.ts
export const permissionGuard: CanActivateFn = (route) => {
  const authStore = inject(AuthStore);
  const router = inject(Router);
  const permission = route.data['permission'] as string;

  if (permission && !authStore.hasPermission(permission)) {
    router.navigate(['/403']);
    return false;
  }

  return true;
};

HTTP Interceptor โ€‹

ts
// interceptors/auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthStore } from '../stores/auth.store';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authStore = inject(AuthStore);
  const token = authStore.token();

  if (token) {
    req = req.clone({
      setHeaders: { Authorization: `Bearer ${token}` },
    });
  }

  return next(req);
};

Page Routes โ€‹

PathPagePermission
/Redirect to /dashboard-
/loginLoginPublic
/registerRegisterPublic
/forgot-passwordForgot passwordPublic
/reset-passwordReset passwordPublic
/dashboardDashboarddashboard:view
/usersUser listusers:list
/users/createCreate userusers:create
/users/:idUser detailsusers:view
/users/:id/editEdit userusers:update
/rolesRole managementroles:list
/permissionsPermission managementpermissions:list
/settingsSystem settingssettings:view
/profileUser profileAuthenticated

Route Configuration โ€‹

ts
// app.routes.ts
import { Routes } from '@angular/router';
import { authGuard } from './guards/auth.guard';
import { permissionGuard } from './guards/permission.guard';

export const routes: Routes = [
  { path: '', redirectTo: 'dashboard', pathMatch: 'full' },

  // Auth routes
  {
    path: '',
    loadComponent: () => import('./components/layout/auth-layout/auth-layout.component'),
    children: [
      { path: 'login', loadComponent: () => import('./pages/auth/login/login.component') },
      { path: 'register', loadComponent: () => import('./pages/auth/register/register.component') },
      { path: 'forgot-password', loadComponent: () => import('./pages/auth/forgot-password/forgot-password.component') },
      { path: 'reset-password', loadComponent: () => import('./pages/auth/reset-password/reset-password.component') },
    ],
  },

  // Admin routes
  {
    path: '',
    loadComponent: () => import('./components/layout/admin-layout/admin-layout.component'),
    canActivate: [authGuard],
    children: [
      {
        path: 'dashboard',
        loadComponent: () => import('./pages/admin/dashboard/dashboard.component'),
        data: { permission: 'dashboard:view' },
        canActivate: [permissionGuard],
      },
      {
        path: 'users',
        loadComponent: () => import('./pages/admin/users/users.component'),
        data: { permission: 'users:list' },
        canActivate: [permissionGuard],
      },
      // ... more routes
    ],
  },
];

UI Components โ€‹

Based on spartan/ui (Angular version of shadcn), 25+ components integrated:

  • Forms: Button, Input, Textarea, Select, Checkbox, RadioGroup, Switch, Slider, DatePicker
  • Data Display: Table, Card, Badge, Avatar, Progress, Skeleton
  • Feedback: Dialog, Sheet, AlertDialog, Toast, Tooltip, Popover
  • Navigation: Tabs, Breadcrumb, Pagination, DropdownMenu, Command
  • Layout: Accordion, Collapsible, ScrollArea, Separator

Theme Configuration โ€‹

ts
// Toggle theme
const uiSettingsStore = inject(UiSettingsStore);
uiSettingsStore.setTheme('dark'); // 'light' | 'dark' | 'system'

// Change skin
uiSettingsStore.setSkin('rose'); // 11 skin presets

Deployment โ€‹

Vercel โ€‹

bash
vercel

Nginx โ€‹

nginx
server {
    listen 80;
    server_name example.com;
    root /var/www/halolight-angular/dist/browser;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api {
        proxy_pass http://backend:3000;
    }
}

Docker โ€‹

dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM nginx:alpine
COPY --from=builder /app/dist/browser /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Comparison with Other Versions โ€‹

FeatureAngular VersionVue VersionNext.js Version
State ManagementNgRx SignalsPiniaZustand
Data FetchingTanStack QueryTanStack QueryTanStack Query
Form ValidationAngular Forms + ZodVeeValidate + ZodReact Hook Form + Zod
Drag-and-Dropangular-gridster2grid-layout-plusreact-grid-layout
UI Componentsspartan/uishadcn-vueshadcn/ui
RoutingAngular RouterVue RouterNext.js App Router
SSRAngular SSRNuxtBuilt-in support