Angular Best Practices
In this article, we will discuss some best practices for Angular development that can help you write cleaner, more maintainable code.
Table of Contents
Open Table of Contents
- Prefer Reactive Forms Over Template-Driven Forms
- Avoid Magic Strings
- Consider leaving one empty line between third party imports and application imports
- Do place properties up top followed by methods
- Use $ for Observable variables
- Services should be Singletons
- Cache HTTP Requests
- Folder by Feature Structure
- Don’t use nested subscriptions
Prefer Reactive Forms Over Template-Driven Forms
Angular provides two ways to work with forms: template-driven forms and reactive forms. While template-driven forms are easier to set up and use, reactive forms offer more flexibility and control over your form data.
Reactive forms are built around observable streams, which allow you to react to changes in your form data and apply transformations to it. This makes it easier to handle complex form scenarios, such as dynamic form fields, form validation, and form submission.
<!-- Bad -->
<input [(ngModel)]="username">
<!-- Good -->
<input [formControl]="usernameControl">
Avoid Magic Strings
Magic strings are hard-coded strings that are used to represent values or keys in your code.
While they may seem harmless at first, magic strings can lead to bugs and maintenance issues down the line.
Instead of using magic strings, consider using constants or enums to represent your values. This makes your code more readable and maintainable, as well as easier to refactor.
// Bad
if (status === 'active') {
// Do something
}
// Good
enum Status {
Active = 'active',
Inactive = 'inactive',
}
if (status === Status.Active) {
// Do something
}
Consider leaving one empty line between third party imports and application imports
This makes it easier to distinguish between third-party imports and application imports, and keeps your code organized and readable.
// Bad
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { UserService } from './user.service';
// Good
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { UserService } from './user.service';
Do place properties up top followed by methods
This makes it easier to find and understand the properties of a component or service, and keeps your code organized and readable.
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css']
})
export class UserComponent {
user: User;
users: User[];
constructor(private userService: UserService) {}
getUser() {
// Get user logic
}
}
Use $ for Observable variables
When working with observables in Angular, it’s a good practice to append a $
to the end of the variable name to indicate that it is an observable.
This makes it easier to identify which variables are observables and helps prevent memory leaks by unsubscribing from observables when they are no longer needed.
customerSubscription$: Subscription;
ngOnInit() {
this.customerSubscription$ = this.customerService.getCustomers().subscribe(customers => {
this.customers = customers;
});
}
ngOnDestroy() {
this.customerSubscription$.unsubscribe();
}
Services should be Singletons
Services in Angular are singletons by default, meaning that there is only one instance of a service throughout the application.
This makes it easy to share data and functionality between different parts of your application, and ensures that changes to the service are reflected across the entire application.
@Injectable({
providedIn: 'root' // This makes the service a singleton
})
export class UserService {
// Service logic
}
Cache HTTP Requests
When making HTTP requests in Angular, it’s a good practice to cache the response data to avoid unnecessary network requests.
You can use the shareReplay
operator from RxJS to cache the response data and share it between multiple subscribers.
// Bad
getUser(id: string) {
return this.http.get('/api/users/' + id);
}
// Good
getUser(id: string): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`).pipe(
catchError(this.handleError),
shareReplay(1)
);
}
Folder by Feature Structure
When structuring your Angular application, consider organizing your files by feature rather than by type.
- Good
src/
|-- app/
| |-- user/
| | |-- user.component.ts
| | |-- user.service.ts
| | |-- user.module.ts
| | |-- user-routing.module.ts
| | |-- user.component.html
| | |-- user.component.css
| |-- post/
| | |-- post.component.ts
| | |-- post.service.ts
| | |-- post.module.ts
| | |-- post-routing.module.ts
| | |-- post.component.html
| | |-- post.component.css
- Bad
src/
|-- app/
| |-- components/
| | |-- user/
| | | |-- user.component.ts
| | | |-- user.component.html
| | | |-- user.component.css
| | |-- post/
| | | |-- post.component.ts
| | | |-- post.component.html
| | | |-- post.component.css
| |-- services/
| | |-- user.service.ts
| | |-- post.service.ts
| |-- modules/
| | |-- user.module.ts
| | |-- post.module.ts
| |-- routing/
| | |-- user-routing.module.ts
| | |-- post-routing.module.ts
Don’t use nested subscriptions
Nested subscriptions can lead to memory leaks and make your code harder to read and maintain.
Instead of using nested subscriptions, consider using operators like switchMap
, mergeMap
, or concatMap
to flatten your observables and avoid nested subscriptions.
// Bad
this.route.params.subscribe(params => {
this.userService.getUser(params.id).subscribe(user => {
this.user = user;
});
});
// Good
this.route.params.pipe(
switchMap(params => this.userService.getUser(params.id))
).subscribe(user => {
this.user = user;
});