DEV Community

Cover image for Angular Tips #3
Khoa Nguyen
Khoa Nguyen

Posted on

Angular Tips #3

Angular Tips

Some Tips for Working with Angular - From a Frontend Developer - Part 3.


Welcome to part 3 of Angular Tips. I hope you find them useful in your process of mastering Angular.

As always, let me know in the comments if you find this interesting to read or if there's anything I can improve. I'd love to hear your feedback!

Note: These tips require prior knowledge and experience with Angular. I will not dive deep into RxJS or other fundamental concepts.


Using the Abstract class to define services includes things in common.

Abstract classes are very powerful in TypeScript and extremely useful when working with Angular. They help enforce structure, promote reusability, and keep your codebase clean and maintainable.

In Angular, there are scenarios where you need to define multiple services that share many common functionalities. In such cases, you can use an abstract class as a base service to inherit and centralize shared logic across different services.

For example, I have two services: book.service.ts and music.service.ts. They are defined as follows:

// Book service

import { Injectable } from "@angular/core";
import { Observable, of } from "rxjs";

type Book = {
  readonly name: string;
  readonly author: string;
};

@Injectable({ providedIn: "root" })
export class BookService {
  protected readonly page$: Observable<readonly Book[]>;

  protected getBooks$(): Observable<readonly Book[]> {
    return of([{ name: "Book 1", author: "Conan" }]);
  }

  protected resetBooks$(): Observable<readonly Book[]> {
    return of([]);
  }

  constructor() {
    this.page$ = this.getBooks$();
  }
}
Enter fullscreen mode Exit fullscreen mode
// Music service

import { Injectable } from "@angular/core";
import { Observable, of } from "rxjs";

type Music = {
  readonly name: string;
  readonly artist: string;
};

@Injectable({ providedIn: "root" })
export class BookService {
  protected readonly page$: Observable<readonly Music[]>;

  protected getMusics$(): Observable<readonly Music[]> {
    return of([{ name: "Music 1", artist: "Edward" }]);
  }

  protected resetMusics$(): Observable<readonly Music[]> {
    return of([]);
  }

  constructor() {
    this.page$ = this.getMusics$();
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, both services have identical functionality. So, how can we generalize them into a base service?
Simple - use an abstract class with a generic type to create a reusable base service!

So, we need to define a general (or base) service like this:

// Base service.

import { Observable, of } from "rxjs";

export abstract class BaseService<T extends Record<string, any>> {
  protected readonly page$: Observable<readonly T[]>;

  protected abstract getList$(): Observable<readonly T[]>;

  protected resetList$(): Observable<readonly T[]> {
    return of([]);
  }

  constructor() {
    this.page$ = this.getList$();
  }
}
Enter fullscreen mode Exit fullscreen mode

In this base service, I keep the page$ observable and the resetList method since they are exactly the same in both services-the only difference is their type. By using a generic type in our base class, we resolve this type difference effectively.

Next, I define an abstract function called getList$(). This function is meant to be implemented differently in each specific service, depending on its purpose. Declaring it as abstract allows each child service to define its own implementation while still inheriting the shared logic from the base service.

Now that we have the base service, the next step is to create our BookService and MusicService, extending from the base service like this:

// Book service
import { Injectable } from "@angular/core";
import { Observable, of } from "rxjs";
import { BaseService } from "./base.service";

type Book = {
  readonly name: string;
  readonly author: string;
};

@Injectable({ providedIn: "root" })
export class BookService extends BaseService<Book> {
  protected getList$(): Observable<readonly Book[]> {
    return of([{ name: "Book 1", author: "Conan" }]);
  }
}
Enter fullscreen mode Exit fullscreen mode
// Music service

import { Injectable } from "@angular/core";
import { Observable, of } from "rxjs";
import { BaseService } from "./base.service";

type Music = {
  readonly name: string;
  readonly artist: string;
};

@Injectable({ providedIn: "root" })
export class BookService extends BaseService<Music> {
  protected getList$(): Observable<readonly Music[]> {
    return of([{ name: "Music 1", artist: "Edward" }]);
  }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, we only need to redefine the abstract getList$() function in each service. Everything else is inherited from the base service, making our code cleaner, more maintainable, and reusable.

Implement Lazy Loading for Optimizing Application Performance, but be careful.

Lazy loading in Angular is a technique used to load feature modules only when needed, reducing the initial bundle size and improving application performance. Instead of loading all modules upfront, Angular loads them dynamically when a user navigates to a specific route.
We can define a lazy load module, like this:

const routes: Routes = [
  {
    path: 'feature',
    loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
  }
];
Enter fullscreen mode Exit fullscreen mode

However, this is not always case where lazy load come to rescue. With lazy load, they will load the module only when it's needed, which mean, as a traceback, the loading time in the first visit of the component require that module will be increased.
When Lazy Loading Can Hurt UX:

  • 🚨 Heavy Pages: If a page has a large bundle size, lazy loading can introduce a noticeable delay, making users think the app is slow or broken.
  • 🚨 Frequent Navigation: If users frequently switch between pages, repeatedly loading modules can be inefficient.
  • 🚨 Above-the-Fold Content: If a page requires immediate rendering of essential content, lazy loading might cause delays.

So, to use lazy loading efficiently, let's combine it with preloadStrategies. To dive deeper into preloadStrategies in Angular, I recommend checking out the blog: Optimize your Angular app's user experience with preloading strategies. This resource is extremely useful for understanding how to choose the right preloadStrategies for your app.


And that's all for blog #3 about the Angular tips! I hope you found these tips helpful. See you in my next blog!

Top comments (0)