This document outlines the best practices implemented throughout the Angular application, ensuring maintainable, performant, and accessible code.
- All components use proper TypeScript interfaces
- No
anytypes used; replaced with proper interfaces - Example:
LoginCredentials,RegisterCredentials,Userinterfaces
- Leveraging TypeScript's type inference where types are obvious
- Explicit typing for complex objects and function parameters
- Replaced all
anytypes with proper interfaces - Using
unknownwhen type is uncertain (not needed in current implementation)
- All components are standalone (no NgModules)
standalone: trueis implied by default in Angular 17+- Clean dependency management through imports
- Local component state managed with signals
- Computed signals for derived state
- Pure and predictable state transformations
Example:
// Local state using signals
isActive = signal(false);
isSubmitting = signal(false);
// Computed signals for derived state
backgroundColor = computed(() => (this.isActive() ? "#e3f2fd" : "#ffffff"));
isFormValid = computed(() => !this.nameError() && !this.emailError());- Feature routes are properly configured for lazy loading
- Auth guard protects routes appropriately
- Ready for implementation when static images are added
- Will use
NgOptimizedImagedirective for all static images
- Each component has a single responsibility
- Components are kept small and focused
- Using
input()andoutput()functions instead of decorators - Type-safe input/output properties
Example:
// Input properties using input() function
title = input<string>("Default Title");
users = input<User[]>([]);
// Output properties using output() function
formSubmitted = output<FormData>();
userSelected = output<User>();- Using
computed()for derived state - Reactive validation and UI state
- All components use
ChangeDetectionStrategy.OnPush - Improved performance through optimized change detection
- Small components use inline templates
- Larger components use external template files
- Using reactive forms with signals
- Template-driven forms avoided in favor of reactive approach
- Using
[class]bindings instead ofngClass - Using
[style]bindings instead ofngStyle
- Component state managed with signals
- Reactive state updates
- Derived state calculated with
computed() - Automatic dependency tracking
- State updates are pure and predictable
- Immutable state patterns
- Templates kept simple and readable
- Complex logic moved to component class
- Using
@if,@for,@switchinstead of*ngIf,*ngFor,*ngSwitch - Modern Angular control flow syntax
Example:
<!-- Using native control flow instead of *ngIf -->
@if (isVisible()) {
<h2>{{ title() }}</h2>
}
<!-- Using native control flow for loops -->
@for (user of users(); track user.id) {
<div class="user-item">
<span>{{ user.name }}</span>
</div>
}
<!-- Switch statement with native control flow -->
@switch (status()) { @case ('loading') {
<div class="loading">Loading...</div>
} @case ('success') {
<div class="success">Success!</div>
} @default {
<div class="default">Default state</div>
} }- Using async pipe to handle observables
- Automatic subscription management
<!-- Using class bindings instead of ngClass -->
<div [class.active]="isActive()" [class.disabled]="isDisabled()">Dynamic classes</div>
<!-- Using style bindings instead of ngStyle -->
<div [style.background-color]="backgroundColor()" [style.color]="textColor()">Dynamic styles</div>- Services designed around a single responsibility
- Clear separation of concerns
- All services use
providedIn: 'root'option - Singleton service pattern
- Using
inject()function instead of constructor injection - Cleaner dependency injection
Example:
@Injectable({
providedIn: "root",
})
export class Auth {
private router = inject(Router);
private notification = inject(NotificationService);
// Service methods...
}- All components use
ChangeDetectionStrategy.OnPush - Reduced change detection cycles
- Efficient reactivity with signals
- Automatic dependency tracking
- Feature modules loaded on demand
- Reduced initial bundle size
- Strict TypeScript configuration
- Proper interface definitions
- No
anytypes
- Consistent use of signals throughout
- Standardized component structure
- Uniform service patterns
- Proper error handling in async operations
- User-friendly error messages
src/app/
├── auth/ # Authentication feature
│ ├── guards/ # Route guards
│ ├── services/ # Auth services
│ └── components/ # Auth components
├── core/ # Core application components
│ ├── layout/ # Main layout
│ ├── header/ # Application header
│ ├── footer/ # Application footer
│ └── sidenav/ # Navigation sidebar
└── shared/ # Shared components and services
├── services/ # Shared services
├── components/ # Reusable components
└── example-component/ # Best practices example
See src/app/shared/example-component/example-component.ts for a comprehensive example demonstrating all best practices:
- ✅ Input/Output functions
- ✅ Signals for state management
- ✅ Computed signals
- ✅ Native control flow
- ✅ Class and style bindings
- ✅ Reactive forms with signals
- ✅ Type safety
- ✅ OnPush change detection
When adding new components or services, ensure they follow these patterns:
- Use
inject()instead of constructor injection - Set
changeDetection: ChangeDetectionStrategy.OnPush - Use signals for local state
- Use
computed()for derived state - Use
input()andoutput()functions - Use native control flow (
@if,@for,@switch) - Use class and style bindings instead of
ngClass/ngStyle - Define proper TypeScript interfaces
- Avoid
anytypes - Use reactive forms with signals
- Keep components small and focused
- Use
providedIn: 'root'for services
This implementation ensures a modern, performant, and maintainable Angular application following current best practices.