ONE DUDE`S BLOG

/media/qj4fbq3wwv0pxasvf57drlggaom.webp

Как создать ленивый компонент в Angular 9+?

17.08.2020
Процесс создания ленивого компонента в Angular 9+ версии. Используем новые возможности Ivy.

До недавнего времени, мы не имели возможность создать компонент с ленивой загрузкой в angular 2+. С девятой версии angular такая возможность появилась.

Зачем вообще нужна ленивая загрузка компонентов?

Ленивая загрузка компонентов позволяет уменьшить bundle вашего приложения. Ваши компоненты будут подгружаться при необходимости отдельными кусочками (chunk). При таком подходе первая загрузка сайта будет осуществляться значительно быстрее.

До 9 версии angular мы использовали ленивый роутинг для загрузки модулей. Такой подход имеет существенный недостаток. Если нам нужно использовать компонент не сразу, а он задекларирован в текущем модуле, он будет загружаться вместе с основной сборкой.

Реализация

Давайте рассмотрим пример лениво загружаемого редактора.
Входные props: имя автора
Выходные outputs: измененный текст

Создадим компонент редактора (без импорта в app.module)

ng g c redactor --flat --skip-import --skip-selector

import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, NgModule } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  template: `
    <h1>Очень тяжелый редактор ^_^</h1>
    <p>Который вызвал: {{ username }}</p>
    <textarea [formControl]="text"></textarea>
  `
})
export class RedactorComponent implements OnInit, OnDestroy {

  @Input() username: string;
  @Output() changed: EventEmitter<string> = new EventEmitter<string>();
  text: FormControl;
  destroyed: Subject<void> = new Subject<void>();

  ngOnInit() {
    this.initEditor();
  }

  public initEditor() {
    this.text = new FormControl('');
    this.text.valueChanges.pipe(takeUntil(this.destroyed)).subscribe(v => {
      this.changed.emit(v);
    })
  }

  ngOnDestroy() {
    this.clearSubscriptions();
  }

  private clearSubscriptions(): void {
    this.destroyed.next();
    this.destroyed.complete();
  }
}

@NgModule({
  imports: [ReactiveFormsModule],
  declarations: [RedactorComponent]
})
class EditorModule {
}

Наш ленивый компонент ничем не отличается от своих задекларированных собратьев. Единственное, мы добавляем модуль с объявлением зависимостей.

Также не забываем отписаться от formControl.valueChanges, во избежание утечек памяти. В данном примере использовался подход с takeUntil и subject.

Теперь можно импортировать наш компонент в app.component

import { Component, ComponentFactoryResolver, ViewContainerRef, ViewChild } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="loadRedactor()">Загрузить ленивый компонент</button>
    <ng-template #redactor></ng-template>
    <p>Напечатали: {{ text }}</p>
  `,
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  @ViewChild('redactor', { read: ViewContainerRef, static: true }) vcr: ViewContainerRef;
  public text: string = '';

  constructor(
    private cfr: ComponentFactoryResolver
  ) { }

  public async loadRedactor(): Promise<void> {
     this.vcr.clear();

    const { RedactorComponent } = await import('./redactor.component');

    const redactor = this.vcr.createComponent(
      this.cfr.resolveComponentFactory(RedactorComponent),
    );
    redactor.instance.username = 'Владислав';
    redactor.instance.changed.subscribe(v => { 
      this.text = v;
     })
  }
}

Разберем этот пример детальнее. Первое что мы делаем - объявляем template, внутрь него мы будем инжектить наш ленивый компонент.

По клику на button мы очищаем предыдущее состояние. Если не очищать контейнер компоненты будут добавляться, нам же необходим лишь 1 редактор.

После чего мы можем асинхронно загрузить наш компонент и создать его внутри нашего темплейта. Далее идет передача input и биндинг output.

После клика на кнопку загрузки редактора мы можем увидеть в networks ленивую загрузку нашего компонента

Как можно улучшить?

Если у вас много ленивых компонентов, можно автоматизировать процесс биндинга input и output каналов, для этого можно воспользоваться, специально написанной, директивой.

Пример можно посмотреть тут

Angular 2+
3
662