Lazy loading services in Angular. What?! Yes, we can.

What is Lazy Loading?

Lazy loading is a code optimization technique that is also called code splitting. It is used to load the code only when it is needed in order to reduce the initial bundle size but also the load time of the application. In order to lazy load code we use the dynamic import syntax.

For example, we can lazy load a module like this:

import("./my-component").then((file) => {
  // do something with the component

By doing this, we are telling the bundler to create a separate bundle for the module and load it only when the code is executed.


Modern Angular

Master the Latest Angular Features to Build Modern Applications. Learn how to use standalone components, signals, the new inject method and much more.

How to Lazy Load a Service in Angular?

Yes, we will use the dynamic import syntax. But, not only that! Because the services in Angular use the Injectable decorator, it means they are injectable and may also depend on other services. So, we cannot just lazy load the service and use it directly as a normal class.

What we have to do is to use the dynamic import syntax to lazy load the service and then use the injector to get the service instance. For example, we can lazy load a service like this:

import("./my-service").then((file) => {
  const service = this.injector.get(file.MyService);
  // do something with the service

This is great, but doesn’t give us a good DX (developer experience).

So, we can create a helper function that will lazy load the service and return the service instance. Here’s a helper function that can help us with that.

export function lazyLoadService<T>(loader: () => Promise<T>): Promise<T> {
  const injector = inject(Injector);

  return loader().then((serviceClass) => {
    const service = injector.get(serviceClass);
    return service;

Let’s return an Observable instead of a Promise, as it’s more convenient to use in Angular, and easier to chain.

export function lazyService<T>(loader: () => Promise<Type<T>>): Observable<T> {
  const injector = inject(Injector);

  return defer(() => {
    return loader().then((service) => injector.get(service));

The defer operator will create an Observable that will execute the loader function only when the Observable is subscribed to. So, we can use it like this:

lazyService(() => import("./my-service")).subscribe((service) => {
  // do something with the service

or better yet, we can pipe the service observable:

lazyService(() => import("./my-service")).pipe(
  concatMap((service) => {
    // do something with the service

Let’s see an example of how to use it in a component:

const DataServiceImport = () => 
  import('./data.service').then((m) => m.DataService);

  template: `
      <li *ngFor="let todo of todos$ | async">
        {{ todo.title }}
  standalone: true,
  imports: [NgFor, AsyncPipe],
export class AppComponent {
  private dataService$ = lazyService(DataServiceImport);

  todos$ = this.dataService$.pipe(concatMap((s) => s.getTodos()));

And now, let’s take a look at the network tab!

network tab.img

Yeah, the service will be in it’s own bundle 🎉!

But what if we want to use the service in another component, we have to lazy load it again. Otherwise, it will be bundled in main bundle (if used in not lazy loaded component), or common bundle (if used in another lazy loaded component), and break the code splitting.

NOTE: The javascript bundle will be downloaded only once, no matter of how many times we lazy load it, because after the first download, webpack (the bundler) will just reuse the downloaded bundled.

Different ways to lazy load a service

Take a look at these tweets by Younes, where he explains different ways to lazy load a service in Angular: What’s the use case for lazy loading a service?

Let’s say we have 2 Services (ServiceOne and ServiceTwo) and 3 Components: ComponentAComponentB, and ComponentC.

  • ComponentA — uses → ServiceOne

  • ComponentB — uses → ServiceOne and ServiceTwo

  • ComponentC — uses → ServiceOne and ServiceTwo

Since ServiceOne and ServiceTwo are being imported __statically__ in all components, Angular (Webpack) puts both Services in a common bundle.

But this common bundle isn’t loaded until we lazy-load one of the components. Now, when we lazy-load ComponentA, common bundle gets loaded but it includes both Services even when ComponentA only uses ServiceOne. (Credits: Chau Tran)

Another use case is when we have very dynamic applications, meaning that we load components dynamically and maybe based on configs, and these components may use different services or the same services. But because we don’t know which services will be used, we can’t bundle them in the main bundle otherwise it will get big! So, we can lazy load the services and load them only when they are needed.


I want to give a shoutout to @yjaaidi for his great research on lazy loading services in Angular. He is the one who inspired me to write this article. So, go ahead and follow him on Twitter.

After Ivy’s rollout, everything in Angular has become easier to work with. Lazy loading services is no exception!