I Built an Angular 21 Admin Template from Scratch — Here's What I Learned
After weeks of work, I just released Nexora, a professional Angular 21 Material Design admin dashboard template. In this article I want to share the key decisions I made, the problems I ran into, and what I learned along the way.
🔗 Live Demo: https://nexora-admin-template-gamma.vercel.app/dashboard/analytics
Why I Built It
I kept starting new Angular projects and spending the first 2 weeks doing the same things every time:
- Setting up routing with lazy loading
- Building a sidebar and header
- Wiring up auth guards and HTTP interceptors
- Configuring Angular Material theming
- Adding charts
So I decided to build a proper starter template once, do it right, and share it with the community.
The Stack
Angular 21 — Standalone components + Signals
Angular Material — UI components
ApexCharts — Interactive charts
TypeScript 5.9 — Strict mode
SCSS — CSS variable theming system
RxJS 7.8 — Reactivity
Key Decision #1 — Standalone Components Only
Angular 21 makes standalone components the default, and for good reason. No more NgModules, no more declarations arrays, no more confusion about where to import what.
Every component in Nexora is standalone:
@Component({
selector: 'nxr-analytics',
standalone: true,
imports: [CommonModule, MatCardModule, NgApexchartsModule],
templateUrl: './analytics.component.html'
})
export class AnalyticsComponent { }
Clean, explicit, easy to understand.
Key Decision #2 — Signals for State
Instead of BehaviorSubjects everywhere, I used Angular Signals for local state management. The ThemeService is a good example:
@Injectable({ providedIn: 'root' })
export class ThemeService {
mode = signal<'light' | 'dark'>('light');
toggle(): void {
const next = this.mode() === 'light' ? 'dark' : 'light';
this.mode.set(next);
document.body.classList.toggle('dark-theme', next === 'dark');
}
}
Reading the value anywhere: this.theme.mode() — no .subscribe(), no memory leaks, no unsubscribe headaches.
Key Decision #3 — CSS Variables for Theming
Instead of Angular Material's complex theming system, I built a simple CSS variable layer on top:
// src/styles/themes/_nexora-theme.scss
:root {
--nxr-primary: #185FA5;
--nxr-primary-dark: #042C53;
--nxr-primary-light: #E6F1FB;
--nxr-accent: #378ADD;
--nxr-sidebar-bg: #0D1B2A;
--nxr-bg: #F4F6FA;
}
.dark-theme {
--nxr-bg: #0F172A;
--nxr-surface: #1E293B;
}
Want to change the entire color scheme? Edit 4 lines. Done.
Key Decision #4 — Lazy Loading Everything
Every route is lazy-loaded. Not just "some" routes — every single one:
{
path: 'dashboard/analytics',
loadComponent: () =>
import('./features/dashboard/analytics/analytics.component')
.then(m => m.AnalyticsComponent)
}
The initial bundle is tiny. Each page only loads when the user navigates to it. Better Core Web Vitals, faster first paint, happier users.
Key Decision #5 — Functional Guards and Interceptors
No more class-based guards. Angular's functional approach is much cleaner:
// auth.guard.ts
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isLoggedIn()) return true;
router.navigate(['/auth/login']);
return false;
};
// auth.interceptor.ts
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(AuthService).getToken();
if (token) {
return next(req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
}));
}
return next(req);
};
Inject what you need, return what you need. No boilerplate.
What's Inside Nexora
3 Dashboards
- Analytics — KPI cards, area chart, donut chart
- eCommerce — Sales stats, orders table with status badges
- CRM — Lead pipeline with Kanban-style columns
6 UI Component Pages
- Buttons & Badges
- Cards
- Tables
- Charts (ApexCharts)
- Dialogs & Alerts
- Timeline
3 Auth Pages
- Login (with show/hide password)
- Register
- Forgot Password
Extra Pages
- Settings (profile + notification toggles)
- Error 404 / 500
The Dark Mode Implementation
Dark mode is a single CSS class toggle on body:
document.body.classList.toggle('dark-theme', isDark);
And in SCSS, every color automatically switches:
.dark-theme {
--nxr-bg: #0F172A;
--nxr-surface: #1E293B;
--nxr-text-primary: #F1F5F9;
--nxr-sidebar-bg: #0A0F1E;
}
No JavaScript color calculations, no flicker, no complexity.
The Biggest Challenge — Peer Dependencies
Angular 21 + ng-apexcharts had a peer dependency conflict that took me a while to debug. The fix was simple once I found it:
# .npmrc
legacy-peer-deps=true
And pinning specific versions in package.json:
"ng-apexcharts": "^1.12.0",
"typescript": "~5.9.0"
Always pin your TypeScript version when using Angular — the compiler is very strict about the version range.
Deploying to Vercel
The vercel.json configuration handles SPA routing automatically:
{
"routes": [
{ "src": "^/[^.]*$", "dest": "/index.html" }
]
}
Without this, refreshing any page gives a 404. With it, Angular's router handles everything correctly.
What I Would Do Differently
-
Start with path aliases from day one — I added
@core/*,@shared/*etc. later and had to update dozens of imports - Mock API from the start — having real HTTP calls from the beginning makes development much smoother
- Write the documentation first — much easier to document while building than after
Try It Yourself
🔗 Live Demo: https://nexora-admin-template-gamma.vercel.app/dashboard/analytics
📦 Get Nexora on Gumroad: https://pedonello.gumroad.com/l/nexora-angular-admin?_gl=1*1y1bdr6*_ga*MTY1ODM5NDk3NC4xNzg3ODE5ODEy*_ga_6LJN6D94N6*czE3ODc4MTk4MTEkbzEkZzEkdDE3ODc4MjE1NjMkajYwJGwwJGgw ($5+ — includes full source + docs)
Default login: demo@nexora.dev / password
The template is production-ready. Clone it, customize the colors in one SCSS file, connect your API, and ship.
Tags
#angular #webdev #typescript #javascript #opensource
Built with Angular 21, Angular Material, ApexCharts, and a lot of coffee ☕
Top comments (0)