DEV Community


Posted on • Originally published at on

Reverse Engineering: NgModuleFactoryLoader in Angular


Lazy loading is a powerful feature of the Angular framework that allows developers to load modules on-demand, as opposed to upfront when the application is first loaded. This can improve performance and reduce the initial load time of the application. At the heart of lazy loading is the NgModuleFactoryLoader, a key mechanism that enables lazy loading in Angular.

In this article, we’ll take a closer look at the NgModuleFactoryLoader and explore how it works behind the scenes. We’ll also provide some code examples to demonstrate how it can be used to lazy load modules in an Angular application.

What is NgModuleFactoryLoader?

The NgModuleFactoryLoader is a service that provides a way to load and instantiate lazy loaded modules in Angular. It’s a key part of the Angular framework’s dependency injection system and is used internally by the router to lazy load modules when required.

The NgModuleFactoryLoader works by providing a mechanism to dynamically load a module factory at runtime. This allows Angular to defer loading of the module until it’s actually required, rather than loading it upfront with the rest of the application.

How does NgModuleFactoryLoader work?

The NgModuleFactoryLoader works by loading module factories on-demand, when they’re requested by the router. When the router navigates to a lazy loaded route, it uses the NgModuleFactoryLoader to load the module factory for the corresponding lazy loaded module.

To do this, the NgModuleFactoryLoader first checks if the module factory is already available in the cache. If it’s not, it uses a custom implementation of the SystemJsNgModuleLoader to load the module factory dynamically at runtime.

The SystemJsNgModuleLoader is a built-in implementation of the NgModuleFactoryLoader that’s provided by Angular. It works by using the SystemJS module loader to load the module factory from a remote file or a bundle. Once the module factory is loaded, it’s cached in memory for future use.

Here’s an example code snippet that demonstrates how to use the SystemJsNgModuleLoader to load a lazy loaded module:

import { NgModuleFactoryLoader, SystemJsNgModuleLoader } from '@angular/core';

// Get a reference to the NgModuleFactoryLoader service
const loader = injector.get(NgModuleFactoryLoader);

// Use the loader to load the module factory for the lazy loaded module
const moduleFactoryPromise = loader.load('path/to/lazy-loaded-module#MyLazyLoadedModule');

// When the promise resolves, use the module factory to create an instance of the module
moduleFactoryPromise.then((moduleFactory) => {
  const moduleRef = moduleFactory.create(injector);
  // Use the module reference to access the lazy loaded module's components and services
Enter fullscreen mode Exit fullscreen mode

In this code, we first get a reference to the NgModuleFactoryLoader service using the injector. We then use the load() method of the loader to asynchronously load the module factory for the lazy loaded module.

The load() method takes a string argument that specifies the path to the lazy loaded module, along with the name of the module itself. In this example, we’re assuming that the lazy loaded module is located at ‘path/to/lazy-loaded-module’ and has a module named MyLazyLoadedModule.

Once the module factory promise resolves, we can use the create() method of the module factory to instantiate the module and get a reference to its components and services.

Indeed, as powerful as lazy loading can be, it’s important to use it judiciously and be mindful of the dependencies between modules. Lazy loaded modules should ideally be self-contained, without any dependencies on other modules that are not also lazy loaded.

If a lazy loaded module does have dependencies on other modules, those dependencies should also be lazy loaded. Otherwise, the performance benefits of lazy loading will be negated by the increased load time of the non-lazy loaded modules.

1. One potential issue to watch out for is circular dependencies. If two modules have dependencies on each other, it can create a circular reference that can cause issues when using lazy loading. To avoid this, it’s best to keep dependencies as linear as possible, with each module only depending on modules that are loaded before it.

2. Another important consideration when using lazy loading is the bundling strategy used by Angular. By default, Angular uses a “just-in-time” (JIT) compilation strategy, which compiles the application on the fly as it’s loaded in the browser. However, this can slow down the initial load time of the application, especially as the codebase grows.

To address this, Angular also provides an “ahead-of-time” (AOT) compilation strategy, which pre-compiles the application during the build process. This can significantly reduce the initial load time of the application, but requires some additional setup and configuration.

In addition to these considerations, there are many other techniques and best practices for optimizing the performance of Angular applications. Some of these include lazy loading third-party libraries, optimizing network requests, and using the Angular CLI’s built-in performance tools.

Best practices for working with NgModuleFactoryLoader

Working with NgModuleFactoryLoader can be a powerful tool in improving the performance and efficiency of your Angular application. Here are some best practices to keep in mind when using it:

  1. Use it only when necessary: Lazy loading modules can be a great way to improve performance, but it’s important to remember that not every module needs to be lazy loaded. Be strategic in deciding which modules to lazy load and which to include in the initial bundle.
  2. Be mindful of module dependencies : Lazy loaded modules should be self-contained and not have any dependencies on other lazy loaded modules. This will help prevent circular dependencies and make it easier to manage the loading order of your modules.
  3. Keep it simple : Try to keep your lazy loaded modules as simple as possible. This will make it easier to manage and debug any issues that may arise.
  4. Use preloading : Preloading modules can be a great way to improve the user experience by loading frequently used modules in the background while the user is using the application. This can help reduce the perceived loading time and make the application feel more responsive.
  5. Monitor the network traffic : Lazy loading modules can increase the amount of network traffic generated by your application. Keep an eye on the network traffic and optimize where possible to minimize the impact on the user experience.
  6. Test thoroughly : Lazy loading modules can introduce additional complexity to your application, so it’s important to test thoroughly to ensure everything is working as expected. Pay special attention to module dependencies and any interactions between lazy loaded modules.

By following these best practices, you can ensure that your use of NgModuleFactoryLoader is effective and efficient, helping to improve the performance and user experience of your Angular application.

The default implementation of NgModuleFactoryLoader in Angular is provided by the SystemJsNgModuleLoader class. This loader is used when Angular is configured to use SystemJS as its module loader.

Here’s an example of how the load() function works internally in the SystemJsNgModuleLoader class:

import { NgModuleFactory, NgModuleFactoryLoader } from '@angular/core';

export class SystemJsNgModuleLoader implements NgModuleFactoryLoader {
  constructor(private _compiler: Compiler) {}

  load(path: string): Promise<NgModuleFactory<any>> {
    // use SystemJS to load the module
    return System.import(path)
      .then((module: any) => {
        // find the module class name by looking for the default export
        const moduleName = Object.keys(module)
          .find(key => typeof module[key] === 'function');
        // use the JIT compiler to create a module factory for the module class
        const moduleFactory = this._compiler.compileModuleSync(module[moduleName]);
        return moduleFactory;
Enter fullscreen mode Exit fullscreen mode

As you can see, the load() function takes a path argument, which is the path to the lazy-loaded module. It then uses the System.import() function to load the module dynamically.

Once the module is loaded, the SystemJsNgModuleLoader uses the JIT (Just-in-Time) compiler provided by the _compiler instance to create a NgModuleFactory for the module class. Finally, it returns the NgModuleFactory wrapped in a Promise.

This NgModuleFactory is then used by the Angular router to create an instance of the lazy-loaded module and render its components.

As we have seen, the NgModuleFactoryLoader plays a crucial role in lazy loading modules in Angular. It allows us to load modules asynchronously, which can greatly improve the performance of our applications. Understanding how it works under the hood can help us write more efficient and optimized code.

However, it’s important to note that the NgModuleFactoryLoader is just one piece of the puzzle when it comes to lazy loading. There are other important considerations such as proper module architecture, bundling strategies, and optimizing the loading of lazy modules.

Overall, lazy loading can be a powerful tool in optimizing the performance of our Angular applications. By taking advantage of the NgModuleFactoryLoader and other techniques, we can ensure that our applications load quickly and efficiently, even as they grow in complexity.

So next time you’re implementing lazy loading in your Angular project, take some time to appreciate the behind-the-scenes magic that makes it all possible. And remember, the NgModuleFactoryLoader is just one part of the larger picture of building high-performance Angular applications.


Notice that prior to Angular 8 you had to use the NgModuleFactoryLoader which was part of the lazy loading “with magic strings” (i.e. loadChildren: './about/about.module#AboutModule') and which is now deprecated. You also don’t need to register the SystemJsNgModuleLoader provider any more.

You can now use directly like this: import(‘./lazy/lazy.module’).then(m => m.LazyModule);

Thanks for reading

Top comments (0)