In this post, we will talk about why you need to clean your code, the best practices for clean and performant Angular applications. We’ll also go through some general coding guidelines to ensure the application is cleaner.
What is a Clean Code, and Why is it Important?
First, what is clean code? A clean code is a code that is easy to read, understand, change, and maintain. Writing clean code is essential to communicate clearly with the next person who works with what your developers have written. The more declarative code is, the better it is to communicate for what it is made. Moreover, a cleaner code is easier to fix if the issue occurs. When writing code, remember these key principles - Keep It Simple, Stupid (KISS), and Don’t Repeat Yourself (DRY).
Related read: Step by Step Guide to Hire Angular Developers
Best Practices for a Clean Code & Performant Angular App
1. trackBy
Use a trackBy function when using ngFor to loop over an array in the template to return a unique identifier for every item.
With a change in the array, Angular re-renders the entire DOM tree. However, when you are using trackBy, Angular easily understands which element has changed and will modify only those selected DOM elements.
// in the template
<li *ngFor="let item of items; trackBy: trackByFn">{{item}}</li>
// in the component
trackByFn(index, item) {
return item.id; // unique id corresponding to the item
}
2. Const vs. let
Use const when the value is not reassigned while declaring variables. Using the let and const when needed and apt makes the intention of declaration clear. Also, when a value is reassigned to a constant accidentally by throwing a compile error, it will identify the issues. Besides, the code’s readability is also enhanced.
// the value of car is not reassigned, so we can make it a const
const car = 'ludicrous car';
let myCar = `My ${car}`;
let yourCar = `Your ${car};
if (iHaveMoreThanOneCar) {
myCar = `${myCar}s`;
}
if (youHaveMoreThanOneCar) {
yourCar = `${youCar}s`;
}
3. Pipeable operators
When using RxJs operators, we use pipeable operators. Pipeable operators are tree-shakeable. By that, we mean that only the code to be executed will be included when imported. You can even identify the unused operators.
import { map, take } from 'rxjs/operators';
iAmAnObservable
.pipe(
map(value => value.item),
take(1)
);
4. Segregate API Hacks
We need to add logic to the code to prepare it for the APIs. So, rather than having the hacks in components, it is better to isolate them in one place. For example, you may use a service from the component for the same. It helps keep the hacks closer to the API so that there is minimum code dealing with the un-hacked code. Furthermore, since all hacks are in one place, it is even easier to find them. That is quite helpful when you need to fix bugs.
5. Subscribe in Template
It is better to avoid subscribing to observable components. Instead, subscribe using the templates. Async pipes unsubscribe automatically, making code simpler and eliminating the risk of forgetting to unsubscribe a subscription in the component. This generally causes a memory leak. It even stops components from introducing bugs where data is mutated outside the subscription.
// template
<p></p>
// component
this.textToDisplay$ = iAmAnObservable
.pipe(
map(value => value.item)
);
6. Subscription Cleaning
Make sure to unsubscribe from them when subscribing to observables. In case you don’t do that, you may lead to unwanted memory leaks as the observable stream is left open. It would be better if you make a lint rule for identifying the observables which are not unsubscribed yet.
Using takeUntil if you wish to listen to the changes before the value of another observable release.
private _destroyed$ = new Subject();
public ngOnInit (): void {
iAmAnObservable
.pipe(
map(value => value.item)
// We want to listen to iAmAnObservable until the component is destroyed,
takeUntil(this._destroyed$)
)
.subscribe(item => this.textToDisplay = item);
}
public ngOnDestroy (): void {
this._destroyed$.next();
this._destroyed$.complete();
}
You can easily manage unsubscribing many observable in the component using a private subject like mentioned here. Use take when you just want to emit the first value of the observable.
iAmAnObservable
.pipe(
map(value => value.item),
take(1),
takeUntil(this._destroyed$)
)
.subscribe(item => this.textToDisplay = item);
Use of take and takeUntil keeps away the memory leaks that can be created when the subscription doesn’t receive the value before the component is destroyed. Otherwise, the subscription would just hang around until it gets its first value.
7. Lazy Load
Lazy load the modules in your Angular app as that would only load something when it is used. It will reduce the app size and improve the app boot time by not loading the unused modules.
// app.routing.ts
{
path: 'lazy-load',
loadChildren: 'lazy-load.module#LazyLoadModule'
}
// lazy-load.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { LazyLoadComponent } from './lazy-load.component';
@NgModule({
imports: [
CommonModule,
RouterModule.forChild([
{
path: '',
component: LazyLoadComponent
}
])
],
declarations: [
LazyLoadComponent
]
})
export class LazyModule {}
8. Avoid Any
It is suggested to always declare variables and constants with a type other than any. This is because without typing when you declare the constants or variables in TypeScript, the typing of both is deduced by the value assigned to them. This causes unwanted issues, however, it can be avoided by typing the variable aptly. Besides, good typings in apps help make refactoring safe and easy.
9. Lint Rules
Tslint has numerous options built-in already to configure in the tslint.json for enforcing specific rules in your codebase. Having lint rules in place helps you get a nice error when you are doing something wrong. Overall the consistency and readability in the app are enforced once you place lint rules aptly.
10. Reusable Components
You must extract the pieces that you think can be reused in a component and instead. However, make a new one. The more dumb the component is, the better it will work for different scenarios. It will then operate purely on the inputs and outputs provided to it. Reusable components minimize the code duplication, allowing you to do changes much faster and maintain it.
11. Pay No Attention to Long Methods
Long methods are a sign of too many things happening, which should be avoided. You must use the Single Responsibility Principle. There is a possibility of method doing something, and the operations could be doing something else inside it. You can extract those methods into their own method, making them do one thing each. Also, long methods are not easy to read, understand, or maintain. They are highly prone to bugs and make refactoring challenging as well.
12. Caching Mechanisms
When you are making API calls, the responses might not change often. You can simply add a caching mechanism and store the value. When a second request is made to the same API, you will get the value in the cache. If not, then make the API call and cache the result. Introduce a cache time if the values don’t change frequently. You can easily check when it was last cached and make your call about whether to use the API call or not. Caching mechanism means avoiding undesirable calls but making it only when required. Also, it avoids the duplication and speed of the app as there is no waiting for the network.
13. Safe Strings
Declare the list of possible values as the type if you have a variable of type stings that can only have one set of values. Thereby, we can avoid dealing with bugs while code writing and compiling rather than taking care of it later during runtime.
14. No Logic in Templates
It is a good idea to extract logic in templates, even if it is the simple one. However, having logic in templates means unit testing is not possible, making it prone to bugs when modifying the template code.
15. DRY or Do Not Repeat Yourself
Never keep the copied code into different places in the codebase. Extract the code that is repeating and replace repeated code with it. The same code in more than one place will make it challenging for you to maintain it and even change. You will have to change it in multiple places where it is saved. Use it only in one place to change and keep one thing to test. Also, less duplicate code means the app will run faster.
Wrapping Up
Angular app development is a long-term and constant journey, and there will always be room for improvement, no matter how clean the code is. Keep these points in mind while creating your app. Your app will be much cleaner, effective, and what app users are expecting.