Working at a large company where we have plenty of web applications with continuous releases and feature improvements taught me the value of implementing good coding practices. With such feature velocity, it is important for the team to do things the best way possible. This helps to reduce technical debt and makes scaling up much less complicated.
Bellow I put some of angular coding standards and practices to improve your angular application performance.
1. Naming Conventions
- File naming:
- Names of folders and files should clearly convey their intent.
- Names should be consistent with the same pattern in which we mention the file’s feature first and then the type, dot separated.
For example consultation.component.ts
or home.component.html
or auth.service.ts
.
- Class and Method naming: It is advisable to use the upper camel case style as a suffix.
For example
DatepickerDirective
,AuthService
,onClick
. - Name variables that are observables with a $ at the end. (There's a debate to use this or not, but I think it's a good way to spot observable variables easier)
2. Project Structure
Creating a folder structure is an important factor we should consider before initiating our project. Our folder structure will easily adapt to the new changes during development.
Keep the structure as flat as possible. We should not create nested structures just for the sake of structures and we should only create subfolders when we feel that the contents of a folder is hard to read at a glance. Keeping the flattest structure possible also makes imports more readable
Keep related code grouped. For example, keep the shared components in a folder, feature components in their feature folder, and so on.
Example:
3. Angular Coding Styles
Here is a set of rules we need to follow to make our project with the proper coding standard.
- Per file, the code must not exceed 400 lines limit.
- Per function, the code must not exceed 75 lines.
- Utilize custom prefixes to prevent element name collisions with components in other apps and with native HTML elements.
- If the values of the variables are intact, declare it with const.
- Names of properties and methods should be in lower camel case.
- Always leave one empty line between imports and modules such as third party and application imports and third-party module and custom module.
- We shouldn’t name our interfaces with the starting capital I letter as we do in some programming languages.
4. Single Responsibility Principle
It is very important not to create more than one component, service, directive… inside a single file. Every file should be responsible for a single functionality. By doing this, we are keeping our files clean, readable, and maintainable.
In the same way, every module, class, or function should have responsibility for a single functionality and it should encapsulate that one part.
5. Lazy Loading
Try to lazy load the modules in an Angular application whenever possible. Lazy loading will load something only when it is used. This will reduce the size of the application load initial time and improve the application boot time by not loading the unused modules.
// app.routing.ts
{
path: 'lazy-load',
loadChildren: 'lazy-load.module#LazyLoadModule'
}
6. Typing
Declare variables or constants with proper types other than any. This will reduce unintended problems. For example use
id: number;
instead ofid: any;
Use
let
rather thanvar
.Declare safe strings: The variable of type string has only some set of values and we can declare the list of possible values as the type. So the variable will accept only the possible values. We can avoid bugs while writing the code during compile time itself.
Use
const
when the variable has a constant value.
7. API Calls Best Practices
Avoid having Subscriptions Inside Subscriptions
Technically, nested subscriptions work, but it is not the most effective way. In case, you want the value to be reused in more than one observable then you can use preferable chaining options like combineLatest
, withLatestFrom
, etc rather than subscribing to one observable in the subscribe block of another observable.
Example:
observable1$.pipe(
withLatestFrom(observable2$),
first()
)
.subscribe(([res1, res2]) => {
console.log(`${res1} & ${res2}`);
});
Isolate API Calls
It is better to isolate API calls in one place, like in a service and use the service from the component. This way we can add logic to these calls closer to the call and independently from the component logic.
Unsubscribe from Observables
When subscribing in your components to RxJS Observables, you should always unsubscribe. Otherwise, this causes unwanted memory leaks as the observable stream is open, even after destroying the component using it.
You can do this in multiple ways:
-Unsubscribe the component in the ngOnDestory
event after destroying the component
-Use the async pipe to subscribe to Observables and automatically unsubscribe in templates.
Subscribe in template
Avoid subscribing to observables from components and instead subscribe to the observables from the template. Here’s why:
It makes the code simpler by eliminating the need to manually manage subscriptions since async pipes unsubscribe themselves automatically. It also reduces the risk of accidentally forgetting to unsubscribe a subscription in the component, which would cause a memory leak. (This risk can also be mitigated by using a lint rule to detect unsubscribed observables.)
Before
// template
<p>{{ textToDisplay }}</p>
// component
iAmAnObservable
.pipe(
map(value => value.item),
takeUntil(this._destroyed$)
)
.subscribe(item => this.textToDisplay = item);
After
// template
<p>{{ textToDisplay$ | async }}</p>
// component
this.textToDisplay$ = iAmAnObservable
.pipe(
map(value => value.item)
);
8. Reusable Component
Components should obey the single responsibility principle. This helps to eliminate code duplication. Components should also only deal with display logic. It is important to separate business logic from template logic.
9. Change Detection Optimizations
Consider adding challenging calculations into the ngDoCheck
lifecycle hook. And if possible cache them for as long as feasible.
10. Use Route Guards on the Navigation
Angular route guards are interfaces provided by angular which when implemented allow us to control the accessibility of a route based on conditions provided in class implementation of that interface.
-
CanActivate
: Checks whether the component can be accessed or not -
CanActivateChild
: Checks whether the child component can be accessed or not -
CanDeactivate
: It asks for permission to discard the changes -
CanLoad
: Checks before load feature module -
Resolve
: It pre-fetch the route data to make sure that data-related navigation is available or not.
11. Use environment variables
Angular provides environment configurations to declare variables unique for each environment. The default environments are development and production. I will upload soon a tutorial on how to configure environment variables.
The major benefits of using environment variables are:
- Easy configuration
- Better security
- Fewer production mistakes
12. Use lint rules
Linting forces the program to be cleaner and more consistent. It is widely supported across all modern editors and can be customized with your own lint rules and configurations.
One very famous and simple example of using Lint rules is to disable console logging in production simply by using "no-console": [true, "log", "error"]
.
A more detailed tutorial on how to configure lint rules is on the way!
13. Documentation
Always document the code, as much as possible. It is a good Angular practice to document methods by defining them using multi-line comments on what task the method actually performs and all parameters should be explained.
/**
* This is the foo function
* @param bar This is the bar parameter
* @returns returns a string version of bar
*/
function foo(bar: number): string {
return bar.toString()
}
Top comments (5)
Nice article!
Although I have a doubt with the 5th point. Correct me if I am wrong.
With the above syntax won't we get the below error -
core.js:6456 ERROR Error: Uncaught (in promise): Error: Cannot find module...
I feel its better to use the promise
import('./progress/progress.module').then(m => m.ProgressModule)
Definitely, newer versions of angular allow to use imports in lazy loading which I also prefer.
Nice Job! Thanks for share this exceptional content. +1
Thank you 😊
In the directory structure you outlined, where would you put your interface files? Ones that could be shared across modules. Good post btw.