Frontend Development Guide
Complete guide for developing the B12 SIS Angular frontend application.
Prerequisites
Node.js 18+ and npm
Angular CLI 19+
IDE with TypeScript support (VS Code recommended)
Project Setup
# Clone repository
git clone <repo-url> b12-frontend
cd b12-frontend
# Install dependencies
npm install
# Start development server
npm start
The development server runs at http://localhost:4200.
Project Structure
b12-frontend/
├── src/
│ ├── app/
│ │ ├── authentication/ # Login, signup, password reset
│ │ ├── config/ # App configuration
│ │ ├── core/ # Core services, guards, interceptors
│ │ │ ├── base/ # Base components (list, detail, create)
│ │ │ ├── guard/ # Route guards
│ │ │ ├── i18n/ # Internationalization
│ │ │ ├── interceptor/ # HTTP interceptors
│ │ │ ├── models/ # Data models
│ │ │ ├── services/ # Core services
│ │ │ └── types/ # TypeScript types
│ │ ├── features/ # Feature modules (75+ modules)
│ │ ├── layout/ # App layouts (main, auth)
│ │ └── shared/ # Shared components, directives, pipes
│ ├── assets/ # Static assets
│ └── environments/ # Environment configurations
├── angular.json # Angular CLI config
├── package.json
└── tsconfig.json
Core Concepts
Base Components
The frontend uses base component classes to standardize CRUD operations:
// Base list component example
export class StudentsListComponent extends BaseListComponent {
override listConfig: GenericListConfig = {
module: 'Student',
title: 'Students',
createRoute: '/module/students/create',
detailRoute: '/module/students',
showActionsColumn: true
};
}
Available base components:
BaseComponent- Root component with destroy$ subjectBaseListComponent- List views with GenericListComponentBaseDetailComponent- Detail/edit viewsBaseCreateComponent- Create forms
HTTP Interceptors
Four interceptors handle cross-cutting concerns:
Interceptor |
Purpose |
|---|---|
|
Adds Bearer token to requests |
|
Adds X-Team-ID header for multi-tenancy |
|
Adds academic session context |
|
Handles HTTP errors globally |
Route Guards
// AuthGuard protects all authenticated routes
{
path: '',
component: MainLayoutComponent,
canActivate: [AuthGuard],
children: [...]
}
Feature Modules
Each feature module follows a consistent structure:
features/students/
├── list/
│ ├── list.component.ts
│ ├── list.component.html
│ └── list.component.scss
├── detail/
│ ├── detail.component.ts
│ ├── detail.component.html
│ └── detail.component.scss
├── create/
│ ├── create.component.ts
│ ├── create.component.html
│ └── create.component.scss
└── routes.ts
Creating a New Feature Module
Create module directory:
ng generate component features/my-module/list
ng generate component features/my-module/detail
ng generate component features/my-module/create
Define routes (
routes.ts):
import { Route } from '@angular/router';
import { ListComponent } from './list/list.component';
import { DetailComponent } from './detail/detail.component';
import { CreateComponent } from './create/create.component';
export const ROUTE: Route[] = [
{ path: 'list', component: ListComponent },
{ path: 'create', component: CreateComponent },
{ path: ':id', component: DetailComponent },
{ path: '**', redirectTo: 'list' }
];
Add to features routes (
features/features.routes.ts):
{
path: 'my_modules',
loadChildren: () => import('./my-module/routes').then((m) => m.ROUTE),
},
Implement list component:
import { Component } from '@angular/core';
import { BaseListComponent } from '@core/base/list.base.component';
import { GenericListConfig } from '@shared/components/generic-list/generic-list.component';
@Component({
selector: 'app-my-module-list',
templateUrl: './list.component.html',
})
export class ListComponent extends BaseListComponent {
override listConfig: GenericListConfig = {
module: 'MyModule',
title: 'My Modules',
createRoute: '/module/my_modules/create',
detailRoute: '/module/my_modules',
showActionsColumn: true,
showCreateButton: true,
showDeleteButton: true
};
}
Core Services
ApiService
Generic HTTP client wrapper:
import { ApiService } from '@core/services/api.service';
@Injectable()
export class MyService {
constructor(private api: ApiService) {}
getItems() {
return this.api.get<Response>('my_modules/list');
}
createItem(data: any) {
return this.api.post<Response>('my_modules', data);
}
updateItem(id: string, data: any) {
return this.api.put<Response>(`my_modules/${id}`, data);
}
deleteItem(id: string) {
return this.api.delete<Response>(`my_modules/${id}`);
}
}
AuthService
Handles authentication state:
import { AuthService } from '@core/services/auth.service';
@Component({...})
export class MyComponent {
constructor(private authService: AuthService) {}
get currentUser() {
return this.authService.currentUserValue;
}
get permissions() {
return this.authService.currentUserValue.permissions;
}
get activeTeamId() {
return this.authService.currentUserValue.active_team_id;
}
}
BackendService
Higher-level API operations:
import { BackendService } from '@core/services/backend.service';
// Generic list with filtering
this.backend.list('students', {
filters: [...],
page: 1,
pageSize: 25
});
Internationalization (i18n)
The app uses ngx-translate for multi-language support:
// In component
constructor(private translate: TranslateService) {}
// Switch language
this.translate.use('vi');
// Get translation
this.translate.instant('students.title');
<!-- In template -->
{{ 'students.title' | translate }}
Languages are loaded from the backend API.
Environment Configuration
// environments/environment.ts (development)
export const environment = {
production: false,
apiUrl: 'http://localhost:8080/api'
};
// environments/environment.prod.ts (production)
export const environment = {
production: true,
apiUrl: 'https://api.school.edu/api'
};
Building & Deployment
# Development build
npm run build:dev
# Production build
npm run build:prod
# Run tests
npm test
# Lint code
npm run lint
Docker Build
# Build image
docker build -t b12-frontend .
# Run container
docker run -p 80:80 b12-frontend
Kubernetes Deployment
# Interactive deployment
make deploy
# Select environment when prompted
Best Practices
Extend base components for consistent behavior
Use lazy loading for all feature modules
Follow naming conventions:
module_namefor paths,ModuleNamefor classesAdd translations for all user-facing text
Use shared components instead of duplicating code
Handle errors via the ErrorInterceptor
Clean up subscriptions using
takeUntil(this.destroy$)
Common Tasks
Adding a Column to GenericList
listConfig: GenericListConfig = {
module: 'Student',
// Add custom columns
extraColumns: [
{ field: 'custom_field', header: 'Custom Field' }
]
};
Custom Row Actions
listConfig: GenericListConfig = {
module: 'Student',
extraActions: [
{ icon: 'assignment', action: 'enroll', tooltip: 'Enroll' }
]
};
onExtraActionClicked(event: { action: any; model: any }) {
if (event.action === 'enroll') {
this.enrollStudent(event.model);
}
}
Accessing Route Parameters
import { ActivatedRoute } from '@angular/router';
constructor(private route: ActivatedRoute) {}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
}