Frontend Architecture

Technical architecture overview of the B12 SIS Angular frontend.

Technology Stack

Technology

Version

Purpose

Angular

19

Core framework

Angular Material

19

UI components

Bootstrap

5

Layout utilities

ngx-translate

Latest

Internationalization

ngx-charts

Latest

Data visualization

FullCalendar

Latest

Calendar views

RxJS

7+

Reactive programming

Application Architecture

┌─────────────────────────────────────────────────────────────┐
│                      App Component                          │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐  ┌─────────────────────────────────┐  │
│  │  Auth Layout    │  │       Main Layout               │  │
│  │  (Login pages)  │  │  ┌─────────────────────────┐    │  │
│  │                 │  │  │        Header           │    │  │
│  │                 │  │  ├─────────────────────────┤    │  │
│  │                 │  │  │ Sidebar │   Content     │    │  │
│  │                 │  │  │         │               │    │  │
│  │                 │  │  │         │  <router-     │    │  │
│  │                 │  │  │         │   outlet>     │    │  │
│  │                 │  │  │         │               │    │  │
│  │                 │  │  └─────────────────────────┘    │  │
│  └─────────────────┘  └─────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Module Architecture

Lazy Loading Strategy

All feature modules are lazy-loaded to optimize initial bundle size:

// app.routes.ts
{
  path: 'module',
  canActivate: [AuthGuard],
  loadChildren: () =>
    import('./features/features.routes').then((m) => m.ROUTE),
}

// features/features.routes.ts
{
  path: 'students',
  loadChildren: () => import('./students/routes').then((m) => m.ROUTE),
}

Feature Modules

75+ feature modules covering all SIS functionality:

Academic Management

  • academic_sessions - Academic years/terms

  • education_levels - Education level definitions

  • grade_levels - Grade/year levels

  • subjects - Subject catalog

  • courses - Course management

  • classes - Class sections

  • timetables - Scheduling

People Management

  • students - Student records

  • teachers - Teacher records

  • guardians - Parent/guardian records

  • users - System users

Enrollment

  • student_enroll_classes - Class enrollment

  • student_enroll_courses - Course enrollment

  • student_enroll_programs - Program enrollment

Grading & Assessment

  • gradebooks - Gradebook management

  • assessments - Assessment definitions

  • assessment_results - Student scores

  • grade_scales - Grading scales

  • report_card_templates - Report card templates

  • student_report_cards - Generated report cards

Attendance & Behavior

  • attendances - Attendance records

  • attendance_configs - Attendance settings

  • behaviors - Behavior incidents

Medical & Health

  • medical_cares - Medical visits

  • medical_care_histories - Medical history

  • medical_timeslots - Appointment scheduling

Administration

  • roles - Role management

  • teams - Multi-tenant teams

  • import_datas - Data import

  • system_settings - System configuration

Core Module

The core/ module provides application-wide services and utilities:

Services

core/services/
├── api.service.ts          # HTTP client wrapper
├── auth.service.ts         # Authentication state
├── backend.service.ts      # Higher-level API operations
├── broadcast.service.ts    # Cross-component communication
├── language.service.ts     # Language management
├── lti-auth.service.ts     # LTI authentication
├── modal.service.ts        # Modal dialogs
├── notification.service.ts # Toast notifications
└── oidc-auth.service.ts    # OIDC/SSO authentication

HTTP Interceptors

core/interceptor/
├── jwt.interceptor.ts              # Bearer token injection
├── team.interceptor.ts             # X-Team-ID header
├── academic-session.interceptor.ts # Academic session context
└── error.interceptor.ts            # Global error handling

Request Flow:

Request → JwtInterceptor → TeamInterceptor → AcademicSessionInterceptor → API
Response ← ErrorInterceptor ← HTTP Response

Route Guards

// auth.guard.ts - Protects authenticated routes
canActivate(route: ActivatedRouteSnapshot): boolean {
  const currentUser = this.authService.currentUserValue;
  if (currentUser && currentUser.token?.access_token) {
    return true;
  }
  this.router.navigate(['/authentication/signin']);
  return false;
}

// module-import.guard.ts - Controls module access
canActivate(route: ActivatedRouteSnapshot): boolean {
  // Check user permissions for module
  return this.hasModuleAccess(route.data['module']);
}

Base Component Pattern

Abstract base components provide consistent behavior:

Component Hierarchy

BaseComponent
├── BaseListComponent    (list views)
├── BaseDetailComponent  (detail/edit views)
└── BaseCreateComponent  (create forms)

BaseComponent

@Component({ template: '' })
export abstract class BaseComponent implements OnDestroy {
  protected destroy$ = new Subject<void>();

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}

BaseListComponent

@Component({ template: '' })
export abstract class BaseListComponent extends BaseComponent {
  abstract listConfig: GenericListConfig;

  @Output() extraActionClick = new EventEmitter<{ action: any; model: any }>();
  @Output() headerExtraActionClick = new EventEmitter<{ action: any }>();

  protected setupTranslations(): void {
    // Auto-translate list configuration
  }

  onExtraActionClicked(event: { action: any; model: any }): void {
    // Override in child components
  }
}

Shared Module

113+ reusable components in shared/:

Component Categories

Data Display

  • generic-list - Configurable data table with sorting, filtering, pagination

  • generic-form - Dynamic form generator

  • editable-field-renderer - Field type renderer

Layout

  • breadcrumb - Navigation breadcrumbs

  • chart-card variants - Dashboard chart cards

Inputs

  • file-upload - S3 file upload

  • fc-* components - Form control wrappers

Custom Widgets

  • attendance-chart - Attendance visualization

  • calendar - Event calendar

  • timetable - Schedule display

State Management

Service-based state with RxJS observables:

@Injectable({ providedIn: 'root' })
export class AuthService {
  private currentUserSubject: BehaviorSubject<Auth>;
  public currentUser: Observable<Auth>;

  constructor() {
    this.currentUserSubject = new BehaviorSubject<Auth>(
      JSON.parse(localStorage.getItem('currentUser') || '{}')
    );
    this.currentUser = this.currentUserSubject.asObservable();
  }

  public get currentUserValue(): Auth {
    return this.currentUserSubject.value;
  }
}

State Flow

User Action → Component → Service → BehaviorSubject → Subscribers
                ↓
              API Call → Backend
                ↓
           Response → Update Subject → UI Update

Multi-Tenancy

Team-based data isolation:

// TeamInterceptor adds header to all requests
request = request.clone({
  setHeaders: {
    'X-Team-ID': `${teamId}`,
  },
});

Team Selection:

// AuthService.setActiveTeam()
setActiveTeam(teamId: string): Observable<Auth> {
  this.currentUserValue.active_team_id = teamId;
  this.currentUserSubject.next(this.currentUserValue);
  localStorage.setItem('currentUser', JSON.stringify(this.currentUserValue));
  return of(this.currentUserValue);
}

Authentication Flow

Standard Login

1. User submits credentials
2. AuthService.login() calls POST /api/auth/login
3. Server returns JWT tokens
4. AuthService stores tokens in localStorage
5. AuthService calls GET /api/auth/me for user details
6. Permissions loaded into auth state
7. JwtInterceptor adds token to subsequent requests

Token Storage

// In AuthService
localStorage.setItem('currentUser', JSON.stringify({
  token: { access_token, refresh_token },
  user: { ... },
  permissions: [...],
  active_team_id: '...'
}));

// In JwtInterceptor
localStorage.setItem('access_token', currentUser.token.access_token);

LTI Authentication

// LtiAuthService handles LTI 1.3 launches
// Tokens provided by LMS platform
localStorage.setItem('lti_mode', 'true');

OIDC/SSO Authentication

// OidcAuthService handles OIDC flows
// Redirects to identity provider
// Processes callback with tokens

Internationalization

ngx-translate Setup

// app.config.ts
TranslateModule.forRoot({
  loader: {
    provide: TranslateLoader,
    useFactory: httpLoaderFactory,
    deps: [HttpClient]
  }
})

Translation Keys

// Component
this.translate.instant('students.list.title')

// Template
{{ 'students.list.title' | translate }}

// With parameters
{{ 'students.enrolled' | translate:{ count: studentCount } }}

Language Switching

// LanguageService
setLanguage(lang: string) {
  this.translate.use(lang);
  localStorage.setItem('lang', lang);
}

Error Handling

ErrorInterceptor

intercept(request: HttpRequest<any>, next: HttpHandler) {
  return next.handle(request).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status === 401) {
        // Token expired - logout
        this.authService.logout();
        this.router.navigate(['/authentication/signin']);
      }
      // Show notification
      this.notification.showError(error.error?.message || 'Error');
      return throwError(() => error);
    })
  );
}

Component-Level Handling

this.api.post('endpoint', data).subscribe({
  next: (response) => {
    this.notification.showSuccess('Saved successfully');
  },
  error: (error) => {
    // ErrorInterceptor handles common cases
    // Custom handling if needed
    console.error('Specific error handling:', error);
  }
});

Performance Optimization

Lazy Loading

All feature modules loaded on-demand reduces initial bundle.

Change Detection

Use OnPush strategy where applicable:

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})

Subscription Management

Always cleanup subscriptions:

this.api.get('endpoint')
  .pipe(takeUntil(this.destroy$))
  .subscribe(...);

TrackBy Functions

Optimize ngFor loops:

<tr *ngFor="let item of items; trackBy: trackById">