You might not be lazy loading properly in Angular. Pitfall of barrel files.
Tags: angular, nx, lazyloading, bundlers
Published: 2024-05-19 17:54:56

A common mistake in many codebases is the assumption that modules are being lazy loaded when, in fact, they are not. The culprit? Barrel files.

Barrel files are widely used, particularly in codebases that utilize NX. But what exactly are barrel files? There is a good chance you have already worked with them.

A barrel file re-exports modules from a folder, and it is conventionally named `index.ts`. For example:

```typescript
// index.ts

export { TransactionsComponent } from './lib/transactions.component';
export { CardsComponent } from './lib/cards.component';
```

In this case, we export `TransactionsComponent` and `CardsComponent` from the library into a single file. This creates a public API that hides all the implementation details and exposes the modules that consumers can use.

This setup seems convenient, but what is the issue with it? Let’s explore this further by creating a banking website (called `demo-app`) that includes a transaction, card, and account page. The account settings page is shared between different apps, not just only `demo-app`.

You would likely organize the transactions and cards pages in one library and the account settings in another library, structured like this:

```typescript
├── apps
│   ├── demo-app
│   │   ├── app stuff
│   ├── other-app
│   │   ├── other app stuff
├── libs
│   ├── demo-app
│   │   ├── feature
│   │   │   ├── card.component.ts
│   │   │   ├── transactions.component.ts
│   │   │   ├── index.ts (barrel file)
│   ├── other-app
│   ├── shared
│   │   ├── feature
│   │   │   ├── account.component.ts
│   │   │   ├── index.ts (barrel file)
└── root stuff
```

When creating a new library with NX, a barrel file is automatically added, and the `tsconfig.base.json` is updated with the appropriate path. This allows the modules to be imported from the public API.

```typescript
// tsconfig.base.json

"paths": {
  "@bundleperf/demo-app/feature": ["libs/demo-app/feature/src/index.ts"],
  "@bundleperf/shared/feature": ["libs/shared/feature/src/index.ts"]
}
```

I created a basic application that displays links to the three different pages. The `loadComponent` function is used to lazy load these pages (standalone components).

```typescript
// app.routes.ts

export const appRoutes: Route[] = [
  {
    path: 'transactions',
    loadComponent: () =>
      import('@bundleperf/demo-app/feature').then(
        (c) => c.TransactionsComponent
      ),
  },
  {
    path: 'cards',
    loadComponent: () =>
      import('@bundleperf/demo-app/feature').then((c) => c.CardsComponent),
  },
  {
    path: 'account',
    loadComponent: () =>
      import('@bundleperf/shared/feature').then((c) => c.AccountComponent),
  },
];
```

![Home page of the demo-app with three links to transactions, cards and account.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdg8qt4dfg4ph80jfbx5.png)

When clicking on the `Transactions` link, Angular lazy loads the `Transactions` component.


![Transactions page of the demo-app with three links to transactions, cards and account. It has a string with “All bank transactions here..”](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0b05lh6nu0alkos1v3gg.png)


![Network activity with a chunk loading in.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ihm5yjsko1dzxdq1cq2.png)

As you can see in the network tab, the chunk containing the `TransactionsComponent` is lazy-loaded. Now, let's navigate to the cards page by clicking on the Cards link.


![The Cards page of the demo-app with three links to transactions, cards and account. It has a string with “All bank cards here..”](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s7o8va0ul65ga5o6fhf3.png)

The page changes, but the network activity remains the same. No new chunk is being loaded. But didn’t we lazy load it? It turns out that the chunk initially loaded includes both `TransactionsComponent` and `CardsComponent`.

![Output of the chunk.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lds9dj19nllaoityx8eb.png)

If we closely examine the chunk, it becomes clear that the `AccountComponent` is not included. Only `TransactionsComponent` and `CardsComponent` are present. Which happens to be both exported in the same barrel file.

To further investigate, we can add the current date to the `CardsComponent` using the Luxon date library.

![Cards component with a date property that gets the current date with the Luxon library.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hi9trjbmwm7qy27rwccv.png)

Refresh the page and revisit the transaction page. Let’s examine the network activity.

![Network activity with a chunk and Luxon library loading in.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/it5xxpeb1z083h0vhsd4.png)

Suddenly, we are also loading the Luxon library, which is only used within the `CardsComponent`. Why? When you import an individual API, all the files in that barrel file must be fetched and transformed because they may contain the imported API and potential side effects that run on initialization. Resulting in loading more files than necessary.

> Note that with the introduction of the defer feature in Angular 17, lazy loading will not occur either.

## Solution
To address this issue, one approach is to avoid using barrel files and instead import modules directly. However, this means sacrificing the ability to hide the implementation details.

A more effective solution is to create libraries with a more granular structure. This involves carefully deciding what components belong together and splitting those that do not. By adopting this approach, you will reap the benefits of improved lazy loading, as well as optimization for NX. With more granular libraries, NX can more precisely determine which code should be linted, tested, built, etc., thereby speeding up local development and pipeline processes.

In a follow-up article, I’ll delve into how you can properly split your code to maximize the benefits of lazy loading, deferred loading, and pipeline tasks. Once it’s complete, I’ll provide a link here or you can follow me for updates.