Master Angular best practices for writing clean, maintainable, and performant applications following industry standards.
Master Angular best practices for writing clean, maintainable, and performant applications following industry standards.
Learn about Project Structure and Organization.
1src/
2├── app/
3│ ├── core/ # Singleton services, guards, interceptors
4│ │ ├── auth/
5│ │ │ ├── auth.guard.ts
6│ │ │ └── auth.service.ts
7│ │ ├── interceptors/
8│ │ │ ├── auth.interceptor.ts
9│ │ │ └── error.interceptor.ts
10│ │ └── services/
11│ │ └── api.service.ts
12│ │
13│ ├── shared/ # Shared components, directives, pipes
14│ │ ├── components/
15│ │ │ ├── button/
16│ │ │ │ ├── button.component.ts
17│ │ │ │ └── button.component.spec.ts
18│ │ │ └── card/
19│ │ │ ├── card.component.ts
20│ │ │ └── card.component.spec.ts
21│ │ ├── directives/
22│ │ │ └── highlight.directive.ts
23│ │ └── pipes/
24│ │ └── truncate.pipe.ts
25│ │
26│ ├── features/ # Feature modules
27│ │ ├── dashboard/
28│ │ │ ├── components/
29│ │ │ ├── services/
30│ │ │ ├── dashboard.component.ts
31│ │ │ └── dashboard.routes.ts
32│ │ └── users/
33│ │ ├── components/
34│ │ ├── models/
35│ │ ├── services/
36│ │ ├── user-list.component.ts
37│ │ └── users.routes.ts
38│ │
39│ ├── app.component.ts
40│ ├── app.routes.ts
41│ └── app.config.ts
42│
43├── assets/
44├── environments/
45│ ├── environment.ts
46│ └── environment.prod.ts
47└── styles/
48 ├── _variables.scss
49 └── styles.scss
50
51// ✓ DO: One component per file
52// ✓ DO: Group related files together
53// ✓ DO: Use feature-based folder structure
54// ✗ DON'T: Mix different concerns in one folder
55// ✗ DON'T: Have deeply nested folder structures (max 7 levels)Learn about Naming Conventions.
1// Components: PascalCase with .component suffix
2export class UserListComponent { } // user-list.component.ts
3
4// Services: PascalCase with .service suffix
5export class AuthService { } // auth.service.ts
6
7// Directives: camelCase selector, PascalCase class
8@Directive({ selector: '[appHighlight]' })
9export class HighlightDirective { } // highlight.directive.ts
10
11// Pipes: camelCase name, PascalCase class
12@Pipe({ name: 'truncate' })
13export class TruncatePipe { } // truncate.pipe.ts
14
15// Interfaces: PascalCase with I prefix (optional)
16export interface User { } // user.model.ts
17export interface IUserService { } // user-service.interface.ts
18
19// Constants: UPPER_SNAKE_CASE
20export const API_BASE_URL = 'https://api.example.com';
21export const MAX_RETRY_ATTEMPTS = 3;
22
23// Private properties: prefix with underscore
24private _internalValue: string;
25
26// Observables: suffix with $
27users$: Observable<User[]>;
28isLoading$ = new BehaviorSubject<boolean>(false);
29
30// Boolean properties: prefix with is/has/should
31isActive: boolean;
32hasPermission: boolean;
33shouldUpdate: boolean;
34
35// Event handlers: prefix with on/handle
36onClick() { }
37handleSubmit() { }
38onUserSelected(user: User) { }Learn about Component Best Practices.
1import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, inject } from '@angular/core';
2import { Subject, takeUntil } from 'rxjs';
3
4// ✓ Use OnPush change detection for better performance
5@Component({
6 selector: 'app-user-list',
7 standalone: true,
8 changeDetection: ChangeDetectionStrategy.OnPush,
9 template: \`
10 <!-- ✓ Use async pipe to auto-unsubscribe -->
11 @for (user of users$ | async; track user.id) {
12 <app-user-card [user]="user"></app-user-card>
13 }
14 \`
15})
16export class UserListComponent implements OnInit, OnDestroy {
17 // ✓ Use inject() function instead of constructor injection
18 private userService = inject(UserService);
19 private destroy$ = new Subject<void>();
20
21 // ✓ Use signals for reactive state
22 selectedUser = signal<User | null>(null);
23 isLoading = signal(false);
24
25 // ✓ Use observables for async data
26 users$ = this.userService.getUsers();
27
28 ngOnInit() {
29 // ✓ Use takeUntil for cleanup
30 this.userService.userUpdated$
31 .pipe(takeUntil(this.destroy$))
32 .subscribe(user => {
33 this.selectedUser.set(user);
34 });
35 }
36
37 ngOnDestroy() {
38 // ✓ Clean up subscriptions
39 this.destroy$.next();
40 this.destroy$.complete();
41 }
42
43 // ✓ Keep methods small and focused
44 onUserSelect(user: User) {
45 this.selectedUser.set(user);
46 }
47
48 // ✓ Use trackBy for performance in loops
49 trackByUserId(index: number, user: User): number {
50 return user.id;
51 }
52}
53
54// ✗ DON'T: Put business logic in components
55// ✗ DON'T: Directly manipulate DOM
56// ✗ DON'T: Forget to unsubscribe from observables
57// ✗ DON'T: Use any type unnecessarilyLearn about Service Best Practices.
1import { Injectable, inject, signal } from '@angular/core';
2import { HttpClient } from '@angular/common/http';
3import { Observable, BehaviorSubject, throwError } from 'rxjs';
4import { catchError, tap, shareReplay } from 'rxjs/operators';
5
6// ✓ Use providedIn: 'root' for singleton services
7@Injectable({
8 providedIn: 'root'
9})
10export class UserService {
11 private http = inject(HttpClient);
12 private apiUrl = inject(API_BASE_URL);
13
14 // ✓ Expose read-only state
15 private usersSignal = signal<User[]>([]);
16 readonly users = this.usersSignal.asReadonly();
17
18 // ✓ Use BehaviorSubject for state that needs initial value
19 private loadingSubject = new BehaviorSubject<boolean>(false);
20 readonly loading$ = this.loadingSubject.asObservable();
21
22 // ✓ Cache observable results with shareReplay
23 private userCache$ = this.http.get<User[]>(\`\${this.apiUrl}/users\`).pipe(
24 shareReplay(1),
25 catchError(this.handleError)
26 );
27
28 getUsers(): Observable<User[]> {
29 return this.userCache$;
30 }
31
32 getUserById(id: number): Observable<User> {
33 return this.http.get<User>(\`\${this.apiUrl}/users/\${id}\`).pipe(
34 tap(user => console.log('User loaded:', user)),
35 catchError(this.handleError)
36 );
37 }
38
39 createUser(user: Partial<User>): Observable<User> {
40 this.loadingSubject.next(true);
41 return this.http.post<User>(\`\${this.apiUrl}/users\`, user).pipe(
42 tap(newUser => {
43 this.usersSignal.update(users => [...users, newUser]);
44 this.loadingSubject.next(false);
45 }),
46 catchError(error => {
47 this.loadingSubject.next(false);
48 return this.handleError(error);
49 })
50 );
51 }
52
53 // ✓ Centralize error handling
54 private handleError(error: any): Observable<never> {
55 console.error('An error occurred:', error);
56 return throwError(() => new Error('Something went wrong'));
57 }
58}
59
60// ✗ DON'T: Make HTTP calls in components
61// ✗ DON'T: Store UI state in services
62// ✗ DON'T: Return subjects directly (expose observables instead)
63// ✗ DON'T: Forget to handle errorsLearn about Performance Optimization.
1// 1. Lazy Loading Routes
2const routes: Routes = [
3 {
4 path: 'users',
5 loadComponent: () => import('./users/user-list.component')
6 .then(m => m.UserListComponent)
7 },
8 {
9 path: 'admin',
10 loadChildren: () => import('./admin/admin.routes')
11 .then(m => m.ADMIN_ROUTES)
12 }
13];
14
15// 2. OnPush Change Detection
16@Component({
17 changeDetection: ChangeDetectionStrategy.OnPush
18})
19export class OptimizedComponent { }
20
21// 3. TrackBy Functions
22@Component({
23 template: \`
24 @for (item of items; track trackById($index, item)) {
25 <div>{{ item.name }}</div>
26 }
27 \`
28})
29export class ListComponent {
30 trackById(index: number, item: any): number {
31 return item.id;
32 }
33}
34
35// 4. Memoization with Signals
36export class UserComponent {
37 users = signal<User[]>([]);
38
39 // Computed signal automatically memoizes
40 activeUsers = computed(() =>
41 this.users().filter(u => u.active)
42 );
43}
44
45// 5. Virtual Scrolling for Large Lists
46import { ScrollingModule } from '@angular/cdk/scrolling';
47
48@Component({
49 imports: [ScrollingModule],
50 template: \`
51 <cdk-virtual-scroll-viewport itemSize="50" class="viewport">
52 @for (item of items; track item.id) {
53 <div class="item">{{ item.name }}</div>
54 }
55 </cdk-virtual-scroll-viewport>
56 \`
57})
58
59// 6. Preloading Strategy
60import { PreloadAllModules, provideRouter } from '@angular/router';
61
62bootstrapApplication(AppComponent, {
63 providers: [
64 provideRouter(routes, withPreloading(PreloadAllModules))
65 ]
66});
67
68// 7. Optimize Bundle Size
69// Import only what you need
70import { map } from 'rxjs/operators'; // ✓ Good
71import * as rxjs from 'rxjs'; // ✗ Bad
72
73// 8. Use Pure Pipes
74@Pipe({
75 name: 'customFilter',
76 pure: true // Default, but be explicit
77})
78export class CustomFilterPipe { }Learn about Security Best Practices.
1// 1. Prevent XSS Attacks
2import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
3
4@Component({
5 template: \`
6 <!-- ✓ Angular sanitizes by default -->
7 <div>{{ userInput }}</div>
8
9 <!-- ✗ Dangerous: bypassing security -->
10 <div [innerHTML]="unsafeHtml"></div>
11
12 <!-- ✓ Safe: sanitized HTML -->
13 <div [innerHTML]="safeHtml"></div>
14 \`
15})
16export class SafeComponent {
17 private sanitizer = inject(DomSanitizer);
18 userInput = '<script>alert("XSS")</script>'; // Automatically escaped
19
20 get safeHtml(): SafeHtml {
21 return this.sanitizer.sanitize(SecurityContext.HTML, this.userInput) || '';
22 }
23}
24
25// 2. HTTP Security Headers (interceptor)
26export const securityInterceptor: HttpInterceptorFn = (req, next) => {
27 const secureReq = req.clone({
28 setHeaders: {
29 'X-Content-Type-Options': 'nosniff',
30 'X-Frame-Options': 'DENY',
31 'X-XSS-Protection': '1; mode=block'
32 }
33 });
34 return next(secureReq);
35};
36
37// 3. Authentication Guard
38export const authGuard: CanActivateFn = (route, state) => {
39 const authService = inject(AuthService);
40 const router = inject(Router);
41
42 if (authService.isAuthenticated()) {
43 return true;
44 }
45
46 // Store attempted URL for redirect after login
47 return router.createUrlTree(['/login'], {
48 queryParams: { returnUrl: state.url }
49 });
50};
51
52// 4. CSRF Protection (enabled by default in Angular)
53import { provideHttpClient, withXsrfConfiguration } from '@angular/common/http';
54
55bootstrapApplication(AppComponent, {
56 providers: [
57 provideHttpClient(
58 withXsrfConfiguration({
59 cookieName: 'XSRF-TOKEN',
60 headerName: 'X-XSRF-TOKEN'
61 })
62 )
63 ]
64});
65
66// 5. Secure Token Storage
67// ✗ DON'T: Store sensitive data in localStorage
68localStorage.setItem('token', 'secret'); // Vulnerable to XSS
69
70// ✓ DO: Use HTTP-only cookies for tokens
71// Set via backend: Set-Cookie: token=...; HttpOnly; Secure; SameSite=Strict
72
73// 6. Input Validation
74export class UserFormComponent {
75 userForm = new FormGroup({
76 email: new FormControl('', [
77 Validators.required,
78 Validators.email,
79 Validators.pattern(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/)
80 ]),
81 password: new FormControl('', [
82 Validators.required,
83 Validators.minLength(8),
84 this.passwordStrengthValidator
85 ])
86 });
87
88 private passwordStrengthValidator(control: AbstractControl) {
89 const value = control.value;
90 const hasNumber = /[0-9]/.test(value);
91 const hasUpper = /[A-Z]/.test(value);
92 const hasLower = /[a-z]/.test(value);
93 const hasSpecial = /[!@#$%^&*]/.test(value);
94
95 const valid = hasNumber && hasUpper && hasLower && hasSpecial;
96 return valid ? null : { passwordStrength: true };
97 }
98}Learn about Testing Best Practices.
1import { TestBed } from '@angular/core/testing';
2import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
3
4// Component Test
5describe('UserListComponent', () => {
6 let component: UserListComponent;
7 let fixture: ComponentFixture<UserListComponent>;
8 let userService: jasmine.SpyObj<UserService>;
9
10 beforeEach(async () => {
11 const userServiceSpy = jasmine.createSpyObj('UserService', ['getUsers']);
12
13 await TestBed.configureTestingModule({
14 imports: [UserListComponent],
15 providers: [
16 { provide: UserService, useValue: userServiceSpy }
17 ]
18 }).compileComponents();
19
20 userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
21 fixture = TestBed.createComponent(UserListComponent);
22 component = fixture.componentInstance;
23 });
24
25 it('should create', () => {
26 expect(component).toBeTruthy();
27 });
28
29 it('should load users on init', () => {
30 const mockUsers = [{ id: 1, name: 'John' }];
31 userService.getUsers.and.returnValue(of(mockUsers));
32
33 fixture.detectChanges();
34
35 expect(userService.getUsers).toHaveBeenCalled();
36 });
37});
38
39// Service Test with HTTP
40describe('UserService', () => {
41 let service: UserService;
42 let httpMock: HttpTestingController;
43
44 beforeEach(() => {
45 TestBed.configureTestingModule({
46 imports: [HttpClientTestingModule],
47 providers: [UserService]
48 });
49
50 service = TestBed.inject(UserService);
51 httpMock = TestBed.inject(HttpTestingController);
52 });
53
54 afterEach(() => {
55 httpMock.verify();
56 });
57
58 it('should fetch users', () => {
59 const mockUsers = [{ id: 1, name: 'John' }];
60
61 service.getUsers().subscribe(users => {
62 expect(users).toEqual(mockUsers);
63 });
64
65 const req = httpMock.expectOne('/api/users');
66 expect(req.request.method).toBe('GET');
67 req.flush(mockUsers);
68 });
69});
70
71// ✓ Write tests for:
72// - Component logic
73// - Service methods
74// - Guards
75// - Pipes
76// - Directives
77// - Error scenarios
78// - Edge casesFollow Angular style guide consistently
Use TypeScript strict mode
Enable ESLint and Prettier for code quality
Write unit tests for all services and components
Use OnPush change detection where possible
Implement lazy loading for routes
Handle errors gracefully with proper user feedback
Unsubscribe from observables to prevent memory leaks
Use trackBy functions in @for loops
Keep components focused and single-responsibility
Validate all user inputs
Use environment files for configuration
Document complex logic with comments
Optimize bundle size with proper imports
Use signals for reactive state management
Consistent code organization and naming conventions improve maintainability
OnPush change detection and lazy loading are essential for performance
Always sanitize user input and use Angular's built-in security features
Keep components thin by delegating business logic to services
Use signals and computed values for reactive state management
Comprehensive testing ensures code quality and prevents regressions
Proper error handling and user feedback improve user experience
Memory leak prevention through proper subscription management is critical
Following Angular style guide ensures consistency across teams and projects
Your progress is saved in this browser, on this device only.