¿Carga de componentes perezosa en angular? ¿Tal vez estamos hablando de módulos de carga diferida utilizando el enrutador angular? No, estamos hablando de los componentes. La versión actual de Angular solo admite la carga lenta de módulos. Pero Ivy le da al desarrollador nuevas oportunidades para trabajar con componentes.

La carga perezosa que usamos hasta ahora: rutas
La carga diferida es un gran mecanismo. En Angular, puede usar este mecanismo sin tener que hacer casi ningún esfuerzo declarando rutas que admitan la carga diferida. Aquí hay un ejemplo de la 
documentación angular 
que demuestra esto:
const routes: Routes = [     { path: 'customer-list',       loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule) }     ]; 
Gracias al código anterior, se creará un fragmento separado para 
customers.module , que se cargará cuando el usuario navegue por la ruta de la 
customer-list .
Esta es una forma muy agradable de reducir el tamaño del paquete principal del proyecto y acelerar la carga inicial de la aplicación.
Pero a pesar de esto, ¿no sería bueno poder controlar con mayor precisión la carga diferida? Por ejemplo, ¿qué pasaría si pudiéramos organizar la carga diferida de componentes individuales?
Hasta ahora, esto no ha sido posible. Pero todo eso cambió con la llegada de Ivy.
Ivy y el concepto de "localidad"
Los módulos son el concepto básico y el componente básico de cada aplicación angular. Los módulos declaran componentes, directivas, tuberías, servicios.
Una aplicación angular moderna no puede existir sin módulos. Una razón para esto es el hecho de que ViewEngine agrega todos los metadatos necesarios a los módulos.
Ivy, por otro lado, adopta un enfoque diferente. En Ivy, un componente puede existir sin un módulo. Esto es posible gracias al concepto de localidad. Su esencia es que todos los metadatos son locales para el componente.
Expliquemos esto analizando el paquete es2015 generado con Ivy.
Paquete Es2015 generado con IvyEn la sección 
Component code , puede ver que el sistema Ivy ha guardado el código de componente. No hay nada especial aquí. Pero luego Ivy agrega algunos metadatos al código.
La primera pieza de metadatos se muestra en la figura como una 
Component factory . La fábrica sabe cómo instanciar un componente. En la sección de 
Component metadata del 
Component metadata , Ivy coloca atributos adicionales, como 
type y 
selectors , es decir, todo lo que el componente necesita durante la ejecución del programa.
Vale la pena mencionar que Ivy agrega la función de 
template aquí. Se muestra en la 
Compiled version of your template sección 
Compiled version of your template . Detengámonos en este hecho interesante con más detalle.
La función de 
template es una versión compilada de nuestro código HTML. Ella sigue las instrucciones de Ivy para crear un DOM. Esto es diferente de cómo funciona ViewEngine.
El sistema ViewEngine toma el código y lo omite. Angular ejecutará el código si lo usamos.
Y con el enfoque utilizado por Ivy, el componente que invoca los comandos angulares se encarga de todo. Este cambio permite que los componentes existan independientemente, esto lleva al hecho de que el algoritmo de sacudida de árbol se puede aplicar al código base angular.
Un ejemplo real de carga de componentes diferidos
Ahora que sabemos que la carga de componentes diferidos es posible, considérelo con un ejemplo real. Es decir, crearemos una aplicación de prueba que le haga preguntas al usuario con opciones de respuesta.
La aplicación muestra una imagen de la ciudad y las opciones entre las que debe elegir el nombre de esta ciudad. Tan pronto como el usuario seleccione una opción haciendo clic en el botón correspondiente, este botón cambiará inmediatamente, indicando si la respuesta fue correcta o incorrecta. Si el fondo del botón se vuelve verde, la respuesta fue correcta. Si el fondo se vuelve rojo, significa que la respuesta fue incorrecta.
Después de recibir la respuesta a la pregunta actual, el programa muestra la siguiente pregunta. Así es como se ve.
Prueba de demostraciónLas preguntas que el programa hace al usuario están representadas por el componente 
QuizCardComponent .
Concepto de carga de componentes perezosos
Primero 
QuizCardComponent la idea general de la carga 
QuizCardComponent componente 
QuizCardComponent .
El proceso de componentes de QuizCardComponentDespués de que el usuario comienza la prueba haciendo clic en el botón 
Start quiz , comenzamos la carga diferida del componente. Una vez que el componente está a nuestra disposición, lo colocamos en un contenedor.
Reaccionamos a la 
questionAnsvered eventos detectados de un componente "vago" de la misma manera que responderíamos a los eventos de un componente regular. Es decir, después de la ocurrencia del evento, mostramos en la pantalla la siguiente tarjeta con la pregunta.
Análisis de código
Para comprender lo que sucede durante la carga diferida de un componente, comenzamos con una versión simplificada de 
QuizCardComponent , que muestra las propiedades de la pregunta.
Luego expandiremos este componente usando componentes de material angular en él. Y al final, ajustaremos la reacción a los eventos generados por el componente.
Organicemos la carga diferida de una versión simplificada del componente 
QuizCardComponent , que tiene la siguiente plantilla:
 <h1>Here's the question</h1> <ul>    <li><b>Image: </b> {{ question.image }}</li>    <li><b>Possible selections: </b> {{ question.possibleSelections.toString() }}</li>    <li><b>Correct answer: </b> {{ question.correctAnswer }}</li> </ul> 
El primer paso es crear un elemento contenedor. Para hacer esto, podemos usar un elemento real, como 
<div> , o - recurrir a 
ng-container , que nos permite prescindir de un nivel adicional de código HTML. Así es como se ve la declaración del elemento contenedor en el que ponemos el componente "vago":
 <mat-toolbar color="primary">  <span>City quiz</span> </mat-toolbar> <button *ngIf="!quizStarted"        mat-raised-button color="primary"        class="start-quiz-button"        (click)="startQuiz()">Start quiz</button> <ng-container #quizContainer class="quiz-card-container"></ng-container> 
En el componente necesita acceder al contenedor. Para hacer esto, usamos la anotación 
@ViewChild y nos 
@ViewChild que queremos leer 
ViewContainerRef .
Tenga en cuenta que en Angular 9, la propiedad 
static en la anotación 
@ViewChild se establece en 
false de forma predeterminada:
 @ViewChild('quizContainer', {read: ViewContainerRef}) quizContainer: ViewContainerRef; 
Ahora tenemos un contenedor en el que queremos agregar el componente "perezoso". A continuación, necesitamos 
ComponentFactoryResolver e 
Injector . Ambos pueden adquirirse recurriendo a la metodología de inyección de dependencia.
La entidad 
ComponentFactoryResolver es un registro simple que establece la relación entre los componentes y las clases 
ComponentFactory generadas automáticamente que se pueden usar para crear instancias de componentes:
 constructor(private quizservice: QuizService,            private cfr: ComponentFactoryResolver,            private injector: Injector) { } 
Ahora tenemos todo lo que se necesita para lograr el objetivo. 
startQuiz en el contenido del método 
startQuiz y organicemos la carga diferida del componente:
 const {QuizCardComponent} = await import('./quiz-card/quiz-card.component'); const quizCardFactory = this.cfr.resolveComponentFactory(QuizCardComponent); const {instance} = this.quizContainer.createComponent(quizCardFactory, null, this.injector); instance.question = this.quizservice.getNextQuestion(); 
Podemos usar el comando 
import ECMAScript para organizar la carga diferida de 
QuizCardComponent . Una expresión de importación devuelve una promesa. Puede trabajar con él utilizando la construcción 
async/await .then o utilizando el controlador 
.then . Después de resolver la promesa, usamos la desestructuración para crear una instancia del componente.
En estos días, debe usar 
ComponentFactory para proporcionar compatibilidad con versiones anteriores. En el futuro, no se requiere la línea correspondiente, ya que podremos trabajar directamente con el componente.
La fábrica 
ComponentFactory nos da 
componentRef . Pasamos 
componentRef e 
Injector al método 
createComponent del contenedor.
El método 
createComponent devuelve un 
ComponentRef donde está contenida la instancia del componente. Usamos 
instance para pasar las 
@Input del componente 
@Input .
En el futuro, todo esto probablemente se puede hacer utilizando el método de 
renderComponent angular 
renderComponent . Este método aún es experimental. Sin embargo, es muy probable que se convierta en un método angular regular. 
Aquí hay materiales útiles sobre este tema.
Esto es todo lo que se necesita para organizar la carga diferida de un componente.
Carga diferida de componentesDespués de presionar el botón 
Start quiz , comienza la carga diferida del componente. Si abre la pestaña 
Network de las herramientas del desarrollador, puede ver el proceso de cargar perezosamente el fragmento de código representado por el archivo 
quiz-card-quiz-card-component.js . Después de cargar y procesar el componente que se muestra, el usuario ve una tarjeta de preguntas.
Extensión de componente
Actualmente estamos cargando el componente 
QuizCardComponent . Esto es muy bueno Pero nuestra aplicación aún no es particularmente funcional.
Arreglemos esto agregando características y componentes adicionales de material angular:
 <mat-card class="quiz-card">  <mat-card-header>    <div mat-card-avatar class="quiz-header-image"></div>    <mat-card-title>Which city is this?</mat-card-title>    <mat-card-subtitle>Click on the correct answer below</mat-card-subtitle>  </mat-card-header>  <img class="image" mat-card-image [src]="'assets/' + question.image" alt="Photo of a Shiba Inu">  <mat-card-actions class="answer-section">    <button [disabled]="answeredCorrectly !== undefined" *ngFor="let selection of question.possibleSelections"            mat-stroked-button color="primary"            [ngClass]="{              'correct': answeredCorrectly && selection === question.correctAnswer,              'wrong': answeredCorrectly === false && selection === question.correctAnswer             }"            (click)="answer(selection)">      {{selection}}    </button>  </mat-card-actions> </mat-card> 
Hemos incluido algunos hermosos componentes materiales en el componente. ¿Qué pasa con los módulos correspondientes?
Por supuesto, se pueden agregar al 
AppModule . Pero esto significa que estos módulos se cargarán en modo "codicioso". Y esta no es una buena idea. Además, el montaje del proyecto fallará, con el siguiente mensaje:
 ERROR in src/app/quiz-card/quiz-card.component.html:9:1 - error TS-998001: 'mat-card' is not a known element: 1. If 'mat-card' is an Angular component, then verify that it is part of this module. 2. If 'mat-card' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. 
Que hacer Como probablemente ya haya entendido, este problema es completamente solucionable. Esto se puede hacer usando módulos.
Pero esta vez los usaremos un poco diferente que antes. 
QuizCardComponent un pequeño módulo al mismo archivo que 
QuizCardComponent (en el archivo 
quizcard.component.ts ):
 @NgModule({  declarations: [QuizCardComponent],  imports: [CommonModule, MatCardModule, MatButtonModule] }) class QuizCardModule { } 
Tenga en cuenta que no hay declaración de 
export .
La especificación de este módulo pertenece exclusivamente a nuestro componente, cargado en modo diferido. Como resultado, el único componente que se declara en el módulo será 
QuizCardComponent . La sección de importación solo importa los módulos que nuestro componente necesita.
No exportamos el nuevo módulo para que los módulos cargados en modo codicioso no puedan importarlo.
Reinicie la aplicación y observe cómo se comporta cuando hace clic en el 
Start quiz .
Análisis de aplicación modificadoGenial El componente 
QuizCardComponent carga en modo 
QuizCardComponent y se agrega al 
ViewContainer . Todas las dependencias necesarias se cargan con él.
Analizaremos el paquete de la aplicación utilizando la 
webpack-bundle-analyze proporcionada por el módulo npm correspondiente. Le permite visualizar el contenido de los archivos que produce Webpack y examinar el esquema resultante.
Análisis de paquetes de aplicacionesEl tamaño del paquete principal de la aplicación es de aproximadamente 260 Kb. Si descargamos el componente 
QuizCardComponent junto con él, el tamaño de los datos descargados sería de aproximadamente 270 Kb. Resulta que pudimos reducir el tamaño del paquete principal en 10 Kb, cargando solo un componente en modo perezoso. Buen resultado!
El código de 
QuizCardComponent después del ensamblado llega a un archivo separado. Si analiza el contenido de este archivo, resulta que no solo 
QuizCardComponent código 
QuizCardComponent , sino también los módulos de Material utilizados en este componente.
Aunque 
QuizCardComponent usa 
MatButtonModule y 
MatCardModule , solo 
MatCardModule al 
MatCardModule código terminado. La razón de esto es que también usamos 
MatButtonModule en el 
AppModule , que muestra el botón 
Start quiz . Como resultado, el código correspondiente cae en otro fragmento del paquete.
Ahora hemos organizado la carga diferida de 
QuizCardComponent . Este componente muestra una tarjeta, diseñada al estilo de Material, que contiene una imagen, una pregunta y botones con opciones de respuesta. ¿Qué sucede ahora si hace clic en uno de estos botones?
El botón, dependiendo de si contiene la respuesta correcta o incorrecta, se vuelve verde o rojo cuando se presiona. ¿Qué más está pasando? Nada Y necesitamos que después de responder una pregunta, se muestre una tarjeta de otra pregunta. Arreglarlo
Manejo de eventos
La aplicación no muestra una nueva tarjeta de preguntas cuando hace clic en el botón de respuesta debido al hecho de que aún no hemos configurado mecanismos para responder a eventos de componentes cargados en modo diferido. Ya sabemos que el componente 
QuizCardComponent genera eventos utilizando 
EventEmitter . Si observa la definición de la clase 
EventEmitter , puede descubrir que es un descendiente del 
Subject :
 export declare class EventEmitter<T extends any> extends Subject<T> 
Esto significa que 
EventEmitter tiene un método de 
subscribe , que le permite configurar la respuesta del sistema a los eventos que ocurren:
 instance.questionAnswered.pipe(    takeUntil(instance.destroy$) ).subscribe(() => this.showNewQuestion()); 
Aquí nos suscribimos a la secuencia 
questionAnswered y llamamos al método 
showNextQuestion , que ejecuta la lógica 
lazyLoadQuizCard :
 async showNewQuestion() {  this.lazyLoadQuizCard(); } 
La 
takeUntil(instance.destroy$) es necesaria para borrar la suscripción que se ejecuta después de que se destruye el componente. Si se 
ngOnDestroy gancho 
ngOnDestroy ciclo de vida del componente 
ngOnDestroy , 
destroy$ se llama con 
next y 
complete .
Como el componente ya está cargado, el sistema no realiza una solicitud HTTP adicional. Usamos el contenido del fragmento de código que ya tenemos, creamos un nuevo componente y lo colocamos en el contenedor.
Ganchos de ciclo de vida de componentes
Casi todos los ganchos del ciclo de vida del componente se 
QuizCardComponent automáticamente cuando se trabaja con el componente 
QuizCardComponent utilizando la técnica de carga diferida. Pero un gancho no es suficiente. ¿Puedes entender cuál?
Ganchos de ciclo de vida de componentesEste es el 
ngOnChanges más importante de 
ngOnChanges . Como nosotros mismos actualizamos las propiedades de entrada de la instancia del componente, también somos responsables de llamar al 
ngOnChanges ciclo de vida 
ngOnChanges .
Para llamar al 
ngOnChanges de la instancia del 
ngOnChanges , debe construir un objeto 
SimpleChange usted mismo:
 (instance as any).ngOnChanges({    question: new SimpleChange(null, instance.question, true) }); 
Llamamos manualmente 
ngOnChanges instancia 
ngOnChanges componente y le pasamos un objeto 
SimpleChange . Este objeto indica que este cambio es el primero, que el valor anterior es 
null y que el valor actual es una pregunta.
Genial Cargamos el componente con módulos de terceros, respondimos a los eventos que generó y configuramos la llamada de los ganchos necesarios del ciclo de vida del componente.
Aquí está el código fuente del proyecto terminado en el que estábamos trabajando.
Resumen
La carga lenta de componentes ofrece a los desarrolladores de Angular grandes oportunidades para optimizar el rendimiento de la aplicación. A su disposición están las herramientas para ajustar muy bien la composición de los materiales cargados en modo perezoso. Anteriormente, cuando era posible cargar solo rutas en modo perezoso, no teníamos tanta precisión.
Desafortunadamente, cuando usamos módulos de terceros en el componente, también debemos cuidar los módulos. Sin embargo, vale la pena recordar que en el futuro esto puede cambiar.
El motor Ivy introdujo el concepto de localidad, gracias al cual los componentes pueden existir independientemente. Este cambio es la base del futuro de Angular.
Estimados lectores! ¿Planea utilizar la técnica de carga de componentes diferidos en sus proyectos angulares?
