Frontend Documentation
Overview
The frontend of this project is built with Angular 19, leveraging modern Angular features including standalone components, signals for state management, the inject() function for dependency injection, and a powerful combination of Angular Material and TailwindCSS for styling.
Technology Stack
- Angular 19
- Standalone components architecture
- Modern dependency injection with
inject()
- Signal-based state management
- Reactive programming with RxJS
- Lazy-loaded routes for optimized performance
- Angular Material 19
- Comprehensive UI component library
- Custom theme configuration
- Dark mode support
- Accessibility features
- TailwindCSS v4
- Utility-first CSS framework
- Integration with Material Design
- Custom color schemes
- Responsive design utilities
- Additional Libraries
- RxJS for reactive programming
- Angular JWT for token handling
Key Architectural Patterns
Standalone Components
The application uses the standalone component pattern for better modularity and tree-shaking:
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
selector: 'app-example',
standalone: true,
imports: [
CommonModule,
MatButtonModule,
RouterModule
],
template: `
<button mat-raised-button color="primary" routerLink="/dashboard">
Dashboard
</button>
`
})
export class ExampleComponent {}
Modern Dependency Injection
Instead of constructor-based dependency injection, this project uses the modern inject()
function:
import { Component, inject } from '@angular/core';
import { UserService } from '@core/services/user.service';
@Component({
selector: 'app-user-profile',
standalone: true,
template: '...'
})
export class UserProfileComponent {
private userService = inject(UserService);
// Component logic using the injected service
}
Signal-Based State Management
The project uses Angular’s signals for state management, providing a reactive and efficient way to handle UI state:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div>Count: 8</div>
<div>Double count: </div>
<button (click)="increment()">Increment</button>
`
})
export class CounterComponent {
// Signal for state
count = signal<number>(0);
// Computed signal for derived state
doubleCount = computed(() => this.count() * 2);
increment() {
this.count.update(value => value + 1);
}
}
Theming with Angular Material and TailwindCSS
The application integrates Angular Material’s theming system with TailwindCSS for a cohesive design experience:
@use "@angular/material" as mat;
html {
color-scheme: light;
@include mat.theme(
(
color: mat.$azure-palette,
typography: Inter,
)
);
}
.dark {
color-scheme: dark;
}
Authentication Flow
Authentication is implemented using JWT tokens with automatic refresh capability:
- User logs in via login form
- JWT token is stored securely
- HTTP interceptor adds token to all API requests
- Auth guard protects routes based on roles and permissions
Project Structure
/frontend
├── src/
│ ├── app/
│ │ ├── @core/ # Core functionality
│ │ │ ├── components/ # Shared components
│ │ │ ├── directives/ # Custom directives
│ │ │ ├── guards/ # Route guards
│ │ │ ├── interceptors/# HTTP interceptors
│ │ │ ├── layout/ # Layout components
│ │ │ ├── models/ # Interfaces and types
│ │ │ ├── pipes/ # Custom pipes
│ │ │ └── services/ # Global services
│ │ ├── feature/ # Feature modules
│ │ │ ├── contact/ # Contact management
│ │ │ └── user/ # User management
│ │ ├── environments/ # Environment config
│ │ └── styles/ # Global styles
│ ├── assets/ # Static assets
│ └── index.html # Main HTML entry
├── Dockerfile # Production container
├── debug.dockerfile # Development container
└── package.json # Dependencies
Key Features
User Authentication
The application includes a complete authentication system with:
- Login with JWT token
- Registration with validation
- Role-based permissions
- Profile management
Role-Based Access Control
Security is implemented using a comprehensive permission system:
// Permission guard
export const PermissionGuard = (pageName: string, operation: string = 'Read'): CanActivateFn => {
return () => {
const permissionService = inject(PermissionService);
// Check if user has required permission
if (permissionService.hasPermission(pageName, operation)) {
return true;
}
return false;
};
};
// Permission directive for UI elements
@Directive({
selector: '[hasPermission]',
standalone: true
})
export class HasPermissionDirective {
private permissionService = inject(PermissionService);
private viewContainer = inject(ViewContainerRef);
private templateRef = inject(TemplateRef<any>);
@Input() set hasPermission(permission: { page: string; operation: string }) {
if (this.permissionService.hasPermission(permission.page, permission.operation)) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
}
}
Contact Management (CRUD Example)
Full CRUD operations for contacts with:
- List view with filtering
- Detail view
- Create/Edit forms
- Delete confirmation
- Permission checks
Dark/Light Theme Support
The application supports dynamic theme switching with seamless integration between Material and Tailwind:
export class ThemeService {
// Theme state
private appTheme = signal<ThemeName>('light');
// Toggle theme
toggleTheme() {
const newTheme = this.appTheme() === 'dark' ? 'light' : 'dark';
this.setTheme(newTheme);
return newTheme;
}
// Apply theme to DOM
private applyThemeEffect = effect(() => {
const theme = this.appTheme();
if (typeof document !== 'undefined') {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
document.documentElement.style.colorScheme = theme;
}
});
}
Development Workflow
Starting the Frontend
Using Docker (recommended):
docker-compose up frontend
Locally:
cd frontend
npm install
npm start
Building for Production
npm run build
Or using Docker:
docker build -f Dockerfile -t contact-frontend .
Testing
npm test
Best Practices
When working with the frontend codebase, follow these guidelines:
- Create standalone components
- Use signals for component state
- Use computed signals for derived state
- Use the inject() function for dependency injection
- Leverage TailwindCSS utilities instead of custom CSS where possible
- Implement proper error handling for API calls
- Follow the permission model for security
- Keep components small and focused
- Use lazy loading for feature modules
- Maintain accessibility standards