Overview
The Rodando Driver project follows strict coding standards to ensure consistency, maintainability, and code quality. We use ESLint, TypeScript strict mode, and Angular style guidelines.
TypeScript Configuration
The project uses strict TypeScript configuration for type safety:
{
"compilerOptions" : {
"strict" : true ,
"noImplicitOverride" : true ,
"noPropertyAccessFromIndexSignature" : true ,
"noImplicitReturns" : true ,
"noFallthroughCasesInSwitch" : true ,
"forceConsistentCasingInFileNames" : true ,
"strictInjectionParameters" : true ,
"strictInputAccessModifiers" : true ,
"strictTemplates" : true
}
}
All TypeScript strict flags are enabled. Never use any without explicit reasoning, and avoid @ts-ignore comments.
Path Aliases
Use the configured path alias for cleaner imports:
// ✅ Good - Using path alias
import { AuthService } from '@/app/core/services/http/auth.service' ;
import { User } from '@/app/core/models/user/user.response' ;
// ❌ Bad - Relative paths
import { AuthService } from '../../../core/services/http/auth.service' ;
import { User } from '../../../../core/models/user/user.response' ;
ESLint Configuration
Angular-Specific Rules
The project enforces Angular ESLint rules:
Component Suffixes
Component Selectors
Directive Selectors
{
"@angular-eslint/component-class-suffix" : [
"error" ,
{
"suffixes" : [ "Page" , "Component" ]
}
]
}
NgRx Rules
All NgRx best practices are enforced:
{
"files" : [ "*.ts" ],
"extends" : [ "plugin:@ngrx/all" ]
}
Run npm run lint to check for violations before committing.
Naming Conventions
Components
Class Names:
Use Component suffix for standard components
Use Page suffix for routable page components
// ✅ Good
export class HomeComponent { }
export class LoginPage { }
export class TripProgressComponent { }
// ❌ Bad
export class Home { }
export class LoginScreen { }
export class TripProgress { }
Selectors:
Use app- prefix
Use kebab-case
// ✅ Good
@ Component ({
selector: 'app-driver-info-modal' ,
selector: 'app-trip-progress' ,
})
// ❌ Bad
@ Component ({
selector: 'driver-info-modal' , // missing prefix
selector: 'appTripProgress' , // camelCase instead of kebab-case
})
Services
Class Names:
Use Service suffix
Descriptive names indicating purpose
// ✅ Good
export class AuthService { }
export class TripApiService { }
export class SecureStorageService { }
export class DriverAvailabilityApiService { }
// ❌ Bad
export class Auth { }
export class Trip { }
export class Storage { }
File Naming:
Use kebab-case
Include service type in name
✅ Good:
auth.service.ts
trip-api.service.ts
driver-ws.service.ts
❌ Bad:
AuthService.ts
tripApi.ts
driver-websocket.ts
State Management
Stores (NgRx Signals):
// ✅ Good - Store naming
export const AuthStore = signalStore ( ... );
export const DriverAvailabilityStore = signalStore ( ... );
export const TripStore = signalStore ( ... );
// ✅ Good - Facade naming
export class AuthFacade { }
export class DriverAvailabilityFacade { }
export class TripFacade { }
Models and Interfaces:
// ✅ Good - Response models
export interface LoginResponse { }
export interface UserProfile { }
export interface TripAssignedResponse { }
// ✅ Good - Payload models
export interface LoginPayload { }
export interface UpdateLocationPayload { }
// ✅ Good - State interfaces
interface AuthState {
accessToken : string | null ;
user : User | null ;
loading : boolean ;
}
Guards and Interceptors
// ✅ Good - Functional guards
export const authGuard : CanActivateFn = ( route , state ) => { };
// ✅ Good - Interceptors
export const authInterceptor : HttpInterceptorFn = ( req , next ) => { };
export const apiErrorInterceptor : HttpInterceptorFn = ( req , next ) => { };
Component Structure
Standalone Components
All new components should be standalone:
import { Component , inject , OnInit } from '@angular/core' ;
import { CommonModule } from '@angular/common' ;
import { IonContent , IonButton } from '@ionic/angular/standalone' ;
@ Component ({
selector: 'app-home' ,
templateUrl: './home.component.html' ,
styleUrls: [ './home.component.scss' ],
standalone: true ,
imports: [ CommonModule , IonContent , IonButton ],
})
export default class HomeComponent implements OnInit {
// Use inject() for dependency injection
private authFacade = inject ( AuthFacade );
private router = inject ( Router );
// Public properties for template
user = this . authFacade . user ;
loading = this . authFacade . loading ;
ngOnInit () {
// Initialization logic
}
// Public methods for template
logout () {
this . authFacade . logout ();
}
}
Key Points:
Prefer inject() over constructor injection
Export as default for routable components
Import only necessary Ionic components
Keep components focused and single-responsibility
Component Organization
Order class members logically:
export class MyComponent {
// 1. Decorators
@ ViewChild ( IonContent ) content !: IonContent ;
@ Input () data ?: any ;
@ Output () save = new EventEmitter ();
// 2. Injected dependencies
private authFacade = inject ( AuthFacade );
private tripService = inject ( TripApiService );
// 3. Public properties (template bindings)
user = this . authFacade . user ;
trips : Trip [] = [];
loading = false ;
// 4. Private properties
private destroy$ = new Subject < void >();
// 5. Lifecycle hooks
ngOnInit () { }
ngOnDestroy () {
this . destroy$ . next ();
this . destroy$ . complete ();
}
// 6. Public methods (template/API)
saveTrip () { }
cancel () { }
// 7. Private helper methods
private loadData () { }
private handleError () { }
}
Service Patterns
HTTP Services
Use consistent patterns for API services:
import { HttpClient } from '@angular/common/http' ;
import { inject , Injectable } from '@angular/core' ;
import { Observable , map , catchError } from 'rxjs' ;
import { environment } from 'src/environments/environment' ;
interface ApiResponse < T > {
success : boolean ;
message ?: string ;
data : T ;
}
@ Injectable ({
providedIn: 'root'
})
export class TripApiService {
private readonly baseUrl = environment . apiUrl ;
private readonly http = inject ( HttpClient );
getById ( tripId : string ) : Observable < Trip > {
const url = ` ${ this . baseUrl } /trips/ ${ tripId } ` ;
return this . http . get < ApiResponse < Trip >>( url ). pipe (
map ( resp => this . unwrap ( resp )),
catchError ( err => this . handleError ( err , url ))
);
}
private unwrap < R extends { data : any }, T >( resp : R ) : T {
if ( ! resp || typeof resp !== 'object' || ! ( 'data' in resp )) {
throw new Error ( 'Unexpected response shape' );
}
return resp . data as T ;
}
private handleError ( err : any , url : string ) {
// Normalize errors
return throwError (() => err );
}
}
Service Best Practices:
Use providedIn: 'root' for singleton services
Centralize API URL construction
Normalize API responses with unwrap() helpers
Handle errors consistently
Return typed Observables
Facade Pattern
Use facades to orchestrate complex business logic:
@ Injectable ({ providedIn: 'root' })
export class AuthFacade {
private readonly authStore = inject ( AuthStore );
private readonly authService = inject ( AuthService );
private readonly secureStorage = inject ( SecureStorageService );
private readonly router = inject ( Router );
// Expose store signals
user = this . authStore . user ;
loading = this . authStore . loading ;
isAuthenticated = computed (() => !! this . authStore . accessToken ());
// Business logic methods
login ( payload : LoginPayload ) : Observable < User > {
this . authStore . setLoading ( true );
return this . authService . login ( payload ). pipe (
tap ( response => this . handleLoginSuccess ( response )),
catchError ( err => this . handleLoginError ( err )),
finalize (() => this . authStore . setLoading ( false ))
);
}
private handleLoginSuccess ( response : LoginResponse ) {
// Complex orchestration logic
}
private handleLoginError ( err : any ) {
// Error handling logic
return throwError (() => err );
}
}
RxJS Best Practices
Observable Naming
Use $ suffix for observables:
// ✅ Good
const user$ = this . authService . getUser ();
const trips$ = this . tripService . getActive ();
private destroy$ = new Subject < void >();
// ❌ Bad
const user = this . authService . getUser ();
const tripsObservable = this . tripService . getActive ();
Subscription Management
Always unsubscribe to prevent memory leaks:
takeUntil Pattern (Recommended)
Async Pipe (Best for Templates)
take(1) for One-Time Operations
export class MyComponent implements OnInit , OnDestroy {
private destroy$ = new Subject < void >();
ngOnInit () {
this . dataService . getData ()
. pipe ( takeUntil ( this . destroy$ ))
. subscribe ( data => {
// Handle data
});
}
ngOnDestroy () {
this . destroy$ . next ();
this . destroy$ . complete ();
}
}
Error Handling
Handle errors gracefully:
this . tripService . getById ( id ). pipe (
catchError ( err => {
// Log error
console . error ( '[TripComponent] Failed to load trip:' , err );
// Show user-friendly message
this . showError ( 'Failed to load trip details' );
// Return fallback value or re-throw
return of ( null );
})
). subscribe ( trip => {
if ( trip ) {
this . trip = trip ;
}
});
Template Conventions
Angular Syntax
<!-- ✅ Good -->
< ion-button (click) = "save()" [disabled] = "loading" >
{{ loading ? 'Saving...' : 'Save' }}
</ ion-button >
< div *ngIf = "user$ | async as user" >
Welcome, {{ user.name }}!
</ div >
< ion-list >
< ion-item *ngFor = "let trip of trips; trackBy: trackById" >
{{ trip.destination }}
</ ion-item >
</ ion-list >
<!-- ❌ Bad -->
< ion-button (click) = "save()" [disabled] = "loading == true" >
{{ loading == true ? 'Saving...' : 'Save' }}
</ ion-button >
< div *ngIf = "user" >
Welcome, {{ user.name }}!
</ div >
< ion-item *ngFor = "let trip of trips" >
{{ trip.destination }}
</ ion-item >
Template Best Practices:
Always use trackBy with *ngFor
Prefer async pipe for observables
Use strict equality (===) in templates
Keep template logic minimal
Type Safety
Interfaces Over Types
Prefer interfaces for object shapes:
// ✅ Good
interface User {
id : string ;
email : string ;
phoneNumber : string | null ;
}
interface LoginPayload {
email : string ;
password : string ;
}
// ⚠️ Use types for unions/primitives
type SessionType = 'web' | 'mobile_app' | 'api_client' ;
type LoadingState = 'idle' | 'loading' | 'success' | 'error' ;
Avoid any
Always provide proper types:
// ✅ Good
function parseResponse < T >( response : ApiResponse < T >) : T {
return response . data ;
}
function handleError ( err : HttpErrorResponse ) : ApiError {
return {
message: err . message ,
status: err . status
};
}
// ❌ Bad
function parseResponse ( response : any ) : any {
return response . data ;
}
function handleError ( err : any ) : any {
return err ;
}
Nullable Types
Be explicit about null/undefined:
// ✅ Good
interface User {
id : string ;
email : string ;
phoneNumber : string | null ; // Explicitly nullable
avatar ?: string ; // Optional property
}
function getUser ( id : string ) : Observable < User | null > {
return this . http . get < User >( `/users/ ${ id } ` ). pipe (
catchError (() => of ( null ))
);
}
// ❌ Bad
interface User {
id : string ;
email : string ;
phoneNumber : string ; // Not clear if nullable
avatar : string ; // Not clear if optional
}
JSDoc for Public APIs
/**
* Authenticates a driver and initializes the session.
*
* @param payload - User credentials
* @returns Observable that emits the authenticated user
* @throws {ApiError} When credentials are invalid
*/
login ( payload : LoginPayload ): Observable < User > {
// Implementation
}
/**
* Normalizes HTTP errors into a consistent ApiError format.
* Handles various error shapes from the backend.
*/
private async normalizeHttpError ( err : HttpErrorResponse ): Promise < ApiError > {
// Implementation
}
Use comments to explain why , not what :
// ✅ Good - Explains reasoning
// Offset by 30s to refresh before token expires
const AUTO_REFRESH_OFFSET = 30_000 ;
// Use cookie-based auth for web sessions to prevent XSS token theft
const usesCookie = sessionType === SessionType . WEB ;
// ❌ Bad - States the obvious
// Set loading to true
this . loading = true ;
// Call the API
this . authService . login ( payload );
Linting
Running ESLint
# Check for linting errors
npm run lint
# Auto-fix issues
ng lint --fix
Pre-commit Hook
Consider setting up a pre-commit hook:
{
"husky" : {
"hooks" : {
"pre-commit" : "npm run lint && npm test"
}
}
}
Summary Checklist
Before committing code, verify:
Consistency is key! When in doubt, follow existing patterns in the codebase.