An amalgamation of coding standards, best practices, and architectural guidelines for maintainable web development with a pinch of personal preference.
How to navigate this repo? It is structured following two approaches:
- This README file is a general overall approach that has a certain amount of depth.
- Other files are divided by categories with more in-depth approaches- they may or may not overlap with some of the general README file points.
- Purpose & Benefits
- Legend
- Quick Reference
- Dependency Management
- TypeScript Guidelines
- Angular Standards
- SCSS/CSS Conventions
- Backend Guidelines
- Universal Principles
- Git & Workflow
- Implementation Guide
These guidelines ensure:
- Consistency - Standardized practices across teams and projects
- Readability - Code that clearly communicates intent and functionality
- Maintainability - Easier debugging, refactoring, and feature addition
- Collaboration - Common conventions reduce onboarding issues
- Scalability - Architectural patterns that support growth
- [MANDATORY] - Non-negotiable standards
- [RECOMMENDED] - Best practices with clear benefits
- [TEAM_PREFERENCE] - Consistency improvements that may vary
- [MANDATORY] Use
const/letinstead ofvar - [MANDATORY] Arrow functions over function expressions
- [MANDATORY] Named error constants instead of magic strings
- [RECOMMENDED] Observable variables end with
$ - [RECOMMENDED] Private methods prefixed with
_ - [RECOMMENDED] ID format:
{object}Id(userId, clientId)
// AVOID
var data = getData();
const process = function(input) { /* ... */ };
throw new Error('Something went wrong');
// PREFER
const data = getData();
const process = (input: InputType): OutputType => { /* ... */ };
throw new Error(PROCESSING_ERROR);# Use npm ci for deterministic builds
npm ci # Preferred
npm i # Avoid in CI/production- Remove unused dependencies regularly to prevent bloat
- Commit
package-lock.jsonto ensure consistent environments - Review dependencies before adding - evaluate necessity and maintenance status
Consistent dependency versions eliminate "works on my machine" issues and security vulnerabilities from outdated packages.
// GOOD
const immutableValue = 'constant';
let mutableValue = 0;
// BAD
var outdatedDeclaration = 'avoid';Rule: Use const for values that won't be reassigned, let for mutable variables.
// GOOD
const add = (a: number, b: number): number => {
return a + b;
};
// BAD
const add = function(a: number, b: number) {
return a + b;
};Benefits: Arrow functions provide lexical this binding and consistent syntax.
// GOOD - Use interfaces for object shapes
interface User {
id: number;
name: string;
email: string;
}
// GOOD - Type aliases for complex types
type EventHandler = (event: Event) => void;
// GOOD - Generics for reusability
interface Repository<T> {
findById(id: number): T | undefined;
}class ChildComponent extends BaseComponent {
override ngOnInit(): void {
super.ngOnInit();
// Child-specific initialization
}
}Purpose: Makes inheritance relationships explicit and catches signature mismatches.
// GOOD
export class UserProfileComponent implements OnInit {
@Input() userId: number;
@Output() userUpdated = new EventEmitter<User>();
private _userService = inject(UserService); // Modern injection
private _loadUserData(): void { /* ... */ }
}
// BAD
export class userProfile {
@Input('id') userId: number;
@Output() updated = new EventEmitter();
}| Situation | Use Constructor | Use inject() |
|---|---|---|
| Few dependencies (1-3) | Clear signature | Optional |
| Many dependencies (4+) | Bloated constructor | Clean fields |
| Functional APIs (guards, resolvers) | Not available | Required |
| Need DI decorators (@Optional, @Self) | Required | Not supported |
<!-- GOOD - Safe navigation for optional properties !! -->
<div>{{ user?.profile?.displayName || 'Guest' }}</div>
<!-- BAD - Potential runtime errors !! -->
<div>{{ user.profile.displayName }}</div>// user.model.ts - Backend alignment
export interface User {
id: number;
firstName: string;
lastName: string;
email: string;
isActive: boolean;
}
// user.view.model.ts - Frontend-specific needs
export interface UserViewModel {
id: number;
fullName: string;
email: string;
displayStatus: 'Active' | 'Inactive';
}// GOOD - Type-safe access
const nameValue = this.userForm.controls['firstName'].value;
// AVOID - Less refactor-friendly
const nameValue = this.userForm.controls.firstName.value;// CONSISTENT
private usersSubject = new BehaviorSubject<User[]>([]);
public users$ = this.usersSubject.asObservable();
private _loadUsers(): void { /* ... */ }Accessibility isn't just compliance; it's about creating inclusive experiences that work for everyone.
Poor accessibility excludes users and creates legal liability.
<!-- SEMANTIC HTML !! -->
<!-- GOOD - Browser provides built-in keyboard support, focus management !! -->
<button (click)="submit()" [disabled]="!isValid">
Submit Form
</button>
<!-- Always associate labels with form controls for screen reader context. !! -->
<label for="email">Email Address</label>
<input id="email" type="email" aria-required="true">
<!-- ARIA ATTRIBUTES !! -->
<span *ngIf="hasError" aria-live="assertive">
{{ errorMessage }}
</span>
<!-- PROPER ALT TEXT !! -->
<img src="user-avatar.jpg" alt="User profile picture">
<img src="decorative-border.png" alt="" aria-hidden="true">
<!-- BAD - Requires manual keyboard handling, no semantic meaning !! -->
<div (click)="submit()" class="fake-button">
Submit Form
</div>- Why: Semantic elements provide automatic keyboard navigation, screen reader context, and focus management. Users with disabilities rely on these built-in behaviors.
Maintain logical heading order for screen reader navigation. <h1> -> <h2> etc...
- Why: Screen readers use headings as navigation landmarks.
Users jump between headings to quickly find content.
An illogical hierarchy breaks this navigation pattern.
- Why: Screen readers announce the label when the input receives focus. Without proper association, users don't know what information to enter.
Use ARIA to provide additional context when HTML semantics aren't sufficient.
<!-- ARIA-LIVE for dynamic content !! -->
<span *ngIf="hasError" aria-live="assertive">
{{ errorMessage }}
</span>
<span *ngIf="saveStatus" aria-live="polite">
Document saved successfully
</span>
<!-- ARIA-LABEL for context without visible text !! -->
<button aria-label="Close modal" (click)="closeModal()">
<i class="icon-close"></i>
</button>
<!-- ARIA-EXPANDED for collapsible content !! -->
<button
[attr.aria-expanded]="isExpanded"
(click)="toggle()"
aria-controls="dropdown-menu">
Options
</button>
<div id="dropdown-menu" [hidden]="!isExpanded">
<!-- menu items !! -->
</div>
- aria-live="assertive" - Interrupts screen reader immediately (errors, urgent alerts)
- aria-live="polite" - Waits for pause in speech (status updates, confirmations)
- aria-atomic="true" - Reads entire region when updated, not just changes
Ensure all interactive elements are keyboard accessible.
<!-- GOOD - Custom interactive element with keyboard support !! -->
<span
role="button"
tabindex="0"
[inlineSVG]="'./assets/icons/clear.svg'"
(click)="clear()"
(keydown.enter)="clear()"
(keydown.space)="clear(); $event.preventDefault()"
aria-label="Clear search">
</span>
<!-- BAD - Click-only interaction !! -->
<span (click)="clear()">
<i class="icon-clear"></i>
</span>
- Why: Many users navigate entirely by keyboard (motor disabilities, power users, mobile screen readers). Every interactive element must be reachable and operable via keyboard.
Provide context-appropriate descriptions for images.
<!-- GOOD - Descriptive alt for meaningful images !! -->
<img src="sales-chart-q4.png" alt="Q4 sales increased 23% from previous quarter">
<!-- GOOD - Empty alt for decorative images !! -->
<img src="decorative-border.png" alt="" aria-hidden="true">
<!-- GOOD - Context-aware descriptions !! -->
<img src="user-avatar.jpg" alt="John Smith's profile picture">
<!-- BAD - Redundant or useless alt !! -->
<img src="chart.png" alt="chart"> <!-- Doesn't describe the data !! -->
<img src="decoration.png" alt="image"> <!-- Screen reader already says "image" !! -->
Alt Text Rules:
- Informative images: Describe the meaning/data, not appearance
- Decorative images: Use alt="" and aria-hidden="true"
- Functional images (icons in buttons): Describe the action, not the icon
- Complex images: Consider longer descriptions or data tables
Testing Accessibility:
- Use Lighthouse audits (Chrome DevTools)
- Test keyboard navigation (Tab, Enter, Space)
- Verify screen reader compatibility (NVDA, JAWS)
Testing Checklist:
- All interactive elements focusable via keyboard
- Focus indicators visible and clear
- Heading structure logical (h1 → h2 → h3)
- Form labels properly associated
- Images have appropriate alt text
- Error messages announced by screen readers
- Color information supplemented with icons/text
- Minimum contrast ratios met
// GOOD - Clear component structure
.c-user-card {
padding: 1rem;
&__header {
font-weight: bold;
}
&__content {
margin-top: 0.5rem;
}
&--featured {
border: 2px solid gold;
}
}
// GOOD - Reusable objects
.o-dropdown {
position: relative;
display: inline-block;
}
// GOOD - Utility classes
.u-text-center { text-align: center; }
.u-margin-small { margin: 0.5rem; }- Components:
.c-component-name - Objects:
.o-object-name - Utilities:
.u-utility-name
// GOOD - Named constants
const INPUT_VALIDATION_ERROR = 'INPUT_VALIDATION_FAILED';
const AUTHENTICATION_ERROR = 'AUTHENTICATION_FAILED';
const PERMISSION_DENIED_ERROR = 'INSUFFICIENT_PERMISSIONS';
// Usage
return BadRequest(INPUT_VALIDATION_ERROR);
// BAD - Magic strings
return BadRequest('Invalid input provided');Benefits:
- Consistent error messages across the application
- Easier localization and translation
- Better error tracking and monitoring
- Reduced typos in error handling
// GOOD - Explicit undefined handling
getUserById(id: number): User | undefined {
return this.users.find(user => user.id === id);
}
// AVOID - Unclear return type
getUserById(id: number): any {
return this.users.find(user => user.id === id);
}- Single Responsibility - Each class/function has one reason to change
- Open/Closed - Open for extension, closed for modification
- Liskov Substitution - Subtypes must be substitutable for base types
- Interface Segregation - Clients shouldn't depend on unused interfaces
- Dependency Inversion - Depend on abstractions, not concretions
- Variables:
camelCasefor most languages - Constants:
UPPER_SNAKE_CASE - Private members: Prefix with
_ - Booleans:
is,has,canprefixes (isValid,hasPermission) - IDs:
{entity}Idformat (userId,orderId)
// GOOD - Clear separation of concerns
class UserService {
// Public API first
public getUsers(): Observable<User[]> { /* ... */ }
public createUser(user: CreateUserRequest): Observable<User> { /* ... */ }
// Private implementation details last
private _validateUser(user: User): boolean { /* ... */ }
private _mapToViewModel(user: User): UserViewModel { /* ... */ }
}# Format: {ticket-id}-brief-description
feature/PROJ-123-user-authentication
bugfix/PROJ-456-form-validation-error
hotfix/PROJ-789-security-patch# Format: [TICKET-ID] Brief description
[PROJ-123] Add user authentication endpoints
[PROJ-456] Fix form validation error handling
[PROJ-789] Update security headers configurationBenefits: Clear traceability between code changes and business requirements.
- Set up linting rules that enforce these standards
- Configure IDE with appropriate extensions and settings
- Create templates for common patterns (components, services)
- Document exceptions when deviating from guidelines
- Audit current code against these guidelines
- Prioritize high-impact changes (error handling, accessibility)
- Refactor incrementally during feature development
- Update gradually to avoid disrupting ongoing work
- Code review checklist based on these guidelines
- Regular team discussions about guideline effectiveness
- Update guidelines based on real-world experience
- Share knowledge through pair programming and documentation
- New framework versions with breaking changes
- Team retrospectives identify pain points
- Industry best practices evolve
- Security vulnerabilities require new approaches
- Propose changes with clear reasoning and examples
- Team review to assess impact and benefits
- Pilot implementation on small features
- Document lessons learned and update accordingly
For questions or suggestions, create an issue or reach out to me. And let that hill connect us