DEV Community

Nello Pedone
Nello Pedone

Posted on

Stop Rewriting the Same Angular Boilerplate — I Did It Once for You

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

📦 Get it: https://pedonello.gumroad.com/l/nexora-angular-admin?_gl=1*1y1bdr6*_ga*MTY1ODM5NDk3NC4xNzg3ODE5ODEy*_ga_6LJN6D94N6*czE3ODc4MTk4MTEkbzEkZzEkdDE3ODc4MjE1NjMkajYwJGwwJGgw (5+)


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
Enter fullscreen mode Exit fullscreen mode

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 { }
Enter fullscreen mode Exit fullscreen mode

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');
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

And in SCSS, every color automatically switches:

.dark-theme {
  --nxr-bg:             #0F172A;
  --nxr-surface:        #1E293B;
  --nxr-text-primary:   #F1F5F9;
  --nxr-sidebar-bg:     #0A0F1E;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And pinning specific versions in package.json:

"ng-apexcharts": "^1.12.0",
"typescript": "~5.9.0"
Enter fullscreen mode Exit fullscreen mode

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" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Without this, refreshing any page gives a 404. With it, Angular's router handles everything correctly.


What I Would Do Differently

  1. Start with path aliases from day one — I added @core/*, @shared/* etc. later and had to update dozens of imports
  2. Mock API from the start — having real HTTP calls from the beginning makes development much smoother
  3. 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)