Deferred loading technique in Angular
Tags: angular, typescript, webdev, tutorial
Published: 2020-05-16 17:47:00

##Introduction

Angular is very powerful framework. It has a lot of stuff to make your product's life much easier. But with great facilities, you get great responsibility.

At my current project, at [CodeGym](https://codegym.cc/) we were faced with the fact that angular produces a significantly big javascript bundle which negatively affects our UX and Page Speed Insights metrics.

You can learn more about this at [Web Vitals](https://web.dev/vitals/)

I suppose you already know about lazyload technique via **router's loadChildren** and code splitting via **one-module-per-component** for shared modules.

In this article I want to tell you one more technique which can help you to make your project better.

##Let's go!

I assume that your already have **@angular/cli** installed.

We will start from scratch. First create new project:

```
ng new example
cd example
```

In the *src/app* folder create our lazy module, with one component.

*lazy.module*
```javascript
@NgModule({
  declarations: [LazyComponent],
})
export class LazyModule {}
```

*lazy.component*
```javascript
@Component({
  selector: "app-lazy",
  template: `
    <div> Hello, I am lazy component!</div>
  `,
})
export class LazyComponent {}
```

Then, we need to create a deferred loading component. It will be a wrapper for our lazy component.

```javascript
@Component({
  selector: "app-deferred-loading",
  template: `<div #container></div>`,
})
export class DeferredLoadingComponent implements OnInit {
  @ViewChild("container", {read: ViewContainerRef}) container: ViewContainerRef;

  constructor(
    private compiler: Compiler,
    private injector: Injector,
  ) { }

  ngOnInit(): void {
    this.load();
  }

  async load(): Promise<void> {
    const { module, component } = await this.getContent();
    const moduleFactory = await this.compiler.compileModuleAsync(module);
    const moduleRef = moduleFactory.create(this.injector);
    const componentFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(component);
    const { hostView, instance } = componentFactory.create(this.injector);
    this.container.insert(hostView);
  }

  private async getContent(): Promise<{ module: any, component: any }> {
    const [moduleChunk, componentChunk] = await Promise.all([
      import("./lazy/lazy.module"),
      import("./lazy/lazy.component")
    ]);
    return {
      module: moduleChunk["LazyModule"],
      component: componentChunk["LazyComponent"]
    };
  }
}
```

We have to load both module and component, because I want to show you how to deal with not single component, but a whole widget with its own services and child components.

Unfortunately, we cannot simply load the code and start using it, because each angular module have its own compilation context. That's why we have to solve this with a [jit compiler](https://angular.io/api/core/Compiler) .

First, we compile a module and resolve its providers.
Second, we resolve component and dynamically inject it in the DOM.

Now we can use it in our *app.component.ts*

```javascript
@Component({
  selector: 'app-root',
  template: `
    <app-deferred-loading *ngIf="isReadyForLazyComponent"></app-deferred-loading>
    <button (click)="load()">Load and bootstrap</button>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  isReadyForLazyComponent: boolean;

  load(): void {
    this.isReadyForLazyComponent = true;
  }
}
```

After button click javascript code loads, compiles and angular renders brand new lazy component.

### Challenge - 1

What if we want to pass some data or even to interact with *app.component* from *lazy.component*?

I do not know if it is the best way to handle such situation, but it works:

1. Modify *app.component* to send data to input and to listen for output

```javascript
@Component({
  selector: 'app-root',
  template: `
    <button (click)="load()">Load and bootstrap</button>
    <app-deferred-loading *ngIf="isReadyForLazyComponent" [props]="props"></app-deferred-loading>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  isReadyForLazyComponent: boolean;

  props = {
    name: "Spike",
    onClick: this.handleLazyComponentClick.bind(this),
  };

  load(): void {
    this.isReadyForLazyComponent = true;
  }

  handleLazyComponentClick(val): void {
    console.log(`${val}: from lazy component!`)
  }
}
```

2.Modify *lazy.component* to recieve and emit data

```javascript
@Component({
  selector: "app-lazy",
  template: `
    <div>
      <hr>
      <div> Hello, I am lazy component!</div>
      <button (click)="handleClick()">Data from child</button>
      <hr>
    </div>
  `,
})
export class LazyComponent {
  @Output() onClick: EventEmitter<string> = new EventEmitter();
  @Input() name: string;

  handleClick(): void {
    this.onClick.emit(`My name is ${this.name}!`);
  }
}
```

3. Then connect app.component and *lazy.component* with *deferred-loading.component*

```javascript
@Component({
  selector: "app-deferred-loading",
  template: `<div #container></div>`,
})
export class DeferredLoadingComponent implements OnInit, OnDestroy {
  ...

  @Input() props: any;

  private isDestroyed$: Subject<void> = new Subject();

  ...

  async load(): Promise<void> {
    ...

    Object.entries(this.props).forEach(([key, value]: [string, any]) => {
      if (instance[key] && instance[key].observers) {
        instance[key]
          .pipe(takeUntil(this.isDestroyed$))
          .subscribe((e) => value(e));
      } else {
        instance[key] = value;
      }
    });

    this.container.insert(hostView);
  }

  private async getContent(): Promise<{ module: any, component: any }> {
    ...
  }

  ngOnDestroy(): void {
    this.isDestroyed$.next();
    this.isDestroyed$.complete();
  }
}
```

Now we can pass data to *lazy.component* input and listen for it's output
It is great. 

### Challenge - 2

What if we need to load our stuff not by click, but by getting into the viewport?

In this case, [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) comes to the rescue.

First, we have to prepare our *app.component*

```javascript
  @Component({
  selector: 'app-root',
  template: `
    <button (click)="load()">Load and bootstrap</button>
    <div class="first-content"></div>
    <app-deferred-loading [props]="props"></app-deferred-loading>
  `,
  styles: [`.first-content {
    background-color: cornflowerblue;
    width: 100%;
    height: 120vh;
  }`]
})
```

Than, edit *deferred-loading.component*

```javascript
...
export class DeferredLoadingComponent implements OnInit, OnDestroy {
  ....

  private intersectionObserver: IntersectionObserver;
  private isDestroyed$: Subject<void> = new Subject();

  constructor(
    private compiler: Compiler,
    private injector: Injector,
    private element: ElementRef,
    @Inject(PLATFORM_ID) private platformId: Object,
  ) { }

  ngOnInit(): void {
    if (isPlatformBrowser(this.platformId)) {
      if ("IntersectionObserver" in window) {
        this.intersectionObserver = this.createIntersectionObserver();
        this.intersectionObserver.observe(this.element.nativeElement);
      } else {
        this.load();
      }
    }
  }

  ...

  private createIntersectionObserver(): IntersectionObserver {
    return new IntersectionObserver(entries => this.checkForIntersection(entries));
  }

  private checkForIntersection(entries: IntersectionObserverEntry[]) {
    entries.forEach((entry: IntersectionObserverEntry) => {
      if (this.isIntersecting(entry)) {
        this.load();
        this.intersectionObserver.unobserve(this.element.nativeElement);
      }
    });
  }

  private isIntersecting(entry: IntersectionObserverEntry): boolean {
    return (<any>entry).isIntersecting && entry.target === this.element.nativeElement;
  } 

  ngOnDestroy(): void {
    ...
    if (this.intersectionObserver) {
      this.intersectionObserver.unobserve(this.element.nativeElement);
    }
  }
}
```

It is standart technique, introduced in [Lazy Loading Images and Video](https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video) .

Now, *lazy.component* will be bootstrapped on the page, only when it gets into the viewport.

I hope my article will help somebody to make his product better. :)

P.S. Source code can be found at [github](https://github.com/IgorFilippov3/deferred-loading-example) .