Best Practices and Guidelines for Angular development
To ensure consistency, maintainability, and high quality in our Angular projects, me with my colleagues have compiled a comprehensive list of Do’s and Don’ts, along with a style guide. Adhering to these guidelines will enhance your development workflow and ensure a uniform codebase.
Some recommendations are common to any programming language and framework.
Do’s:
1. Follow Angular Style Guide: Adhere to the official Angular Style Guide (https://angular.io/guide/styleguide) for best practices and conventions.
2. Use consistent naming. Choose the names of variables, methods, classes, and files as carefully as possible. Detail names.
Example: instead of item, use something like someTestItem for variables and some-test-item for files part.
3. Follow the single responsibility principle. One component / service / method should serve one purpose.
High-quality code is characterized by its division into concise, well-named blocks, each serving a single, easily understandable purpose.
4. Component-Based Architecture: Structure your application using a modular, component-based architecture.
5. Lazy Loading: Utilize lazy loading to improve application performance.
6. Use Services for the Logic: Keep components light and delegate business logic to services.
7. Reactive Programming: Embrace reactive programming with RxJS for handling asynchronous operations.
Prefer using RxJS objects (Observable, BehaviorSubject, Subject) instead of synchronous variables in components and services (not component inputs). It ensures async-safe code and prevents issues with change detection.
8. Use “$” sign for Observable name. Example: isLoaded$
9. Unit Testing: Write unit tests for your components and services to ensure stability and catch issues early.
10. Proper Error Handling: Implement proper error handling mechanisms to provide a robust user experience.
11. Use Angular CLI: Utilize Angular CLI for generating boilerplate code, ensuring consistency.
12. Optimize for Production: Ensure that your application is optimized for production with proper build configurations.
13. Use Akita ng-devtools / redux-devtools to monitor Store.
14. Carefully monitor API (Application Programming Interface) calls in the console/network to avoid duplicated API requests.
15. Use takeUntil(this.onDestroy$) or takeUntilDestroyed() for component subscriptions. Async pipe unsubscribes on its own.
16. Use Akita/NgRx Store for work with collections coming from an API call.
17. If you use Akita/NgRx Store for the entity, do not create Observable directly from the method that makes API call. Use selectors to get data
from the Store.
18. If you do not use Akita/NgRx Store for the entity, use shareReplay(1) for Observable, created from API call, because each subscription to such Observable (e.g. async pipe in template or using this Observable in combineLatest and similar RxJS operator) calls a new request.
No need to use shareReplay(1) for a single direct subscription on API call.
For multiple subscriptions use Observables.
19. Avoid nested subscriptions in RxJS pipe. Use higher-order mapping operators like switchMap and mergeMap to prevent nested subscriptions.
20. Prefer to use interfaces instead of classes if your goal is just to describe the Model, because it is hard to keep classes immutable.
21. Prefer using typed reactive forms instead of template forms.
Avoid using UntypedFormBuilder, UntypedFormGroup, UntypedFormControl types.
22. Interfaces, Constants, Enums should be divided into files with appropriate names and be in the appropriate folders
Example: /consts/date-format.const.ts
Such folders should contain index.ts to define a module;
23. Keep templates small and simple. Break complex templates into components.
24. Minimize component logic. Keep components focused on presentation, delegate business logic and data retrieval to services.
25. Use Smart (Container) and Dumb (Presentation) Components for Improved Code Organization.
Don’ts:
1. Avoid Deep Nesting: Keep your component and function nesting to a minimum to enhance readability.
2. Avoid Any: Do not use the any type unnecessarily; always try to define proper types (There may be an exception such as tests, where some data should be presented partially, there may be any cast to make correct test work fine).
3. Do not Ignore Change Detection: Be mindful of Angular’s change detection mechanism to avoid performance issues. Use OnPush change detection strategy.
If you had to use ChangeDetectionStrategy.Default, avoid calling complex logic in templates, because Angular recalculates it every change-detection loop.
4. Avoid Large Components: Refrain from creating overly large components; keep them focused and manageable.
5. Avoid Magic Numbers and Strings: Replace magic numbers and strings with well-named constants or Enums. https://en.wikipedia.org/wiki/Magic_number_(programming)
6. Avoid Unnecessary Dependencies: Be mindful of adding third-party dependencies; only add what is necessary.
7. Do not Ignore Performance: Continuously monitor and optimize the performance of your application.
8. Do not use public access modifier for the component methods and properties, it is already public by default in Typescript, but always use private if the method / property can be encapsulated.
9. Do not assign undefined, use null instead.
10. Try to avoid observable subscriptions in services.
11. Static properties and methods should not be added to services because it is not Angular way. (It is not possible to use InjectionToken with them hence we cannot benefit from dependency injection)
12. Do not use component methods for data output or conditions in template, use RxJS Objects or Angular Pipes instead.
13. Strictly do not use methods that return Observables in template — such methods generate new Observable each change detection loop.
Use the following order of elements in the component:
// Import Statements
import { Component, Input, OnInit } from '@angular/core';
import { SomeService } from 'src/app/services/some.service';
// Component Decorator
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
// Class Declaration
export class ExampleComponent implements OnInit {
// Input/Output Properties
@Input() publicProperty: string;
// Private Properties
private internalData: Idata;
// Public Properties
commonData: Idata = null;
// Constructor
constructor(private someService: SomeService) { }
// Lifecycle Hooks
ngOnInit(): void {
this.internalData = this.someService.getData();
}
// Getters/Setters
get processedData(): Idata {
return this.internalData.process();
}
// Public Methods
doSomething(): void {
// ...
}
// Private Methods
private helperFunction(): void {
// ...
}
}