Skip to content

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

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.

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
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;
});