Usar Lazy load en componentes Angular

antoniocardenas

Antonio Cardenas

Posted on October 29, 2020

Usar Lazy load en componentes Angular

con ivy en Angular 9

Lazy loading en componentes Angular? 🤔Quizás quieras decir módulos lazy loading con el router de Angular!

¡No, lo has leído correctamente , lazy loading en componentes!

Si, la versiónes pre ivy de Angular solo soportan lazy loading de módulos . Pero Ivy abre un nuevo mundo de posibilidades.

Lazy loading hasta ahora— Rutas Lazy loaded

El Lazy loading es una gran característica. En Angular, lo obtenemos casi gratis al declarar una ruta lazy.

Lazy loading a module in Angular / source: angular.io

El código anterior generaría un fragmento separado para el customers.module que se carga tan pronto como llegamos a la ruta de customer-list.
Es una forma muy bonita de reducir el tamaño de su paquete principal y aumentar la carga inicial de su aplicación.
Aún así, ¿no sería genial si tuviéramos un control aún más granular sobre el lazy loading? Por ejemplo, ¿mediante el lazy loading de componentes individuales?
El lazy loading de componentes individuales no ha sido posible hasta ahora. Pero las cosas han cambiado con Ivy.

🌱 Ivy introduce “localidad”.

Los módulos son un concepto de primera clase y el componente principal de todas las aplicaciones de Angular. Declaran varios componentes, directivas, pipes y servicios.

Las aplicaciones Angular de hoy no pueden existir sin módulos. Una de las razones de esto es que el ViewEngine agrega todos los metadatos necesarios a los módulos.

Ivy, por otro lado, adopta otro enfoque. En Ivy, un componente puede existir sin un módulo. Gracias a un concepto llamado “Localidad”.

"Localidad" significa que todos los metadatos son locales al componente.

Permítanme explicar esto echando un vistazo más de cerca a un paquete es2015 generado con Ivy.

es2015 bundle generated with Ivy 🌱

En la sección "Código de componente", podemos ver que Ivy mantiene nuestro código de componente. Nada especial. Pero luego Ivy también le agrega algunos metadatos.

El primer metadato que agrega es un Factory que sabe cómo instanciar nuestro componente ("Component Factory"). En la parte de "Metadatos de componentes", Ivy agrega más atributos como type, selector, etc., todo lo que necesita en tiempo de ejecución.

Una de las cosas más interesantes que agrega Ivy es la función de template. Lo que merece algunas explicaciones adicionales.

La función de plantilla es la versión compilada de nuestro HTML. Ejecuta las instrucciones de Ivy para crear nuestro DOM. Esto difiere de la forma en que funcionaba ViewEngine.

El ViewEngine tomó nuestro código y lo repitió. Angular estaba ejecutando código si lo estábamos usando.

Con el enfoque Ivy, el componente está en el asiento del conductor y ejecuta Angular. Este cambio permite que un componente viva por sí solo y hace que el núcleo angular se le pueda aplicar tree-shaking

Un ejemplo del mundo real de un componente Lazy Loading

Un ejemplo del mundo real de carga diferida de un componente
Ahora que sabemos que la carga diferida es posible, lo demostraremos en un caso de uso del mundo real. Vamos a implementar una aplicación Quiz.
La aplicación muestra una imagen de la ciudad con diferentes posibles soluciones. Una vez que un usuario elige una solución, el botón en el que se hace clic muestra inmediatamente si la respuesta fue correcta o no volviéndose rojo o verde.
Después de responder una pregunta, aparece la siguiente pregunta. Aquí tienes una vista previa rápida:

El concepto de de un componente lazy loading👨‍🎓

Primero, ilustremos la idea general de la carga diferida de nuestro componente QuizCard.

Una vez que el usuario inicia el quiz haciendo clic en el botón "Start Quiz", comenzamos a cargar nuestro componente usando lazy load. Una vez que tengamos el componente, lo agregaremos a un contenedor.

Reaccionamos a los eventos de salida questionAnwsered de nuestro componente lazy-loaded como lo hacemos con los componentes estándar. Una vez que ocurre el evento de salida questionAnwsered, agregamos una nueva tarjeta Quiz.

Entendido, echemos un vistazo al código 🔍

Para explicar el proceso de carga diferida de un componente, comenzaremos con una versión simplificada de nuestro QuizCardComponent que muestra de manera simplista las propiedades de la pregunta.

Luego, ampliaremos nuestro componente agregando componentes de Material angular. Por último, pero no menos importante, reaccionamos a los eventos de salida de nuestro componente de carga diferida.

Entonces, por ahora, carguemos una versión simplificada del QuizCardComponent que tiene la siguiente plantilla:

El primer paso es crear un elemento contenedor. Para esto, usamos un elemento real como un div o podemos usar un ng-container, que no introduce un nivel extra de HTML.

Genial, tenemos el contenedor donde queremos agregar nuestro componente lazy-loaded. A continuación, necesitamos un ComponentFactoryResolver y un Injector que podemos utilizar ambos mediante inyección de dependencia.

Un ComponentFactoryResolver es un registro simple que asigna componentes a clases de ComponentFactory generadas que se pueden usar para crear instancias de componentes.

Ok, en este punto, tenemos todas las cosas que necesitamos para lograr nuestro objetivo. Cambiemos nuestro método startQuiz y carguemos nuestro componente de manera lazy load.

Podemos usar la función import de ECMAScript para usar lazy load en nuestro QuizCardComponent. La declaración de importación nos devuelve una promesa que manejamos usando async/await o con un controlador then. Una vez que la promesa se resuelve, usamos la desestructuración para hacer grep al componente.

No use async/await cuando compile en es2017. Zone js no puede parchear declaraciones nativas async/await. Por lo tanto, puede tener problemas con la detección de cambios. Si compila su código en es2017, debe usar un controlador .then con una función de devolución de llamada.

Para ser compatible con versiones anteriores, hoy en día necesitamos un ComponentFactory. Esta línea no será necesaria en el futuro ya que podemos trabajar directamente con el componente.

El ComponentFactory nos da un componentRef que luego, junto con el Injector, pasaremos al método createComponent de nuestro contenedor.

El createComponent nos devuelve un ComponentRef que contiene una instancia de nuestro componente. Usamos esta instancia para pasar las propiedades de @Input a nuestro componente.

En el futuro, todo esto podría hacerse utilizando el método renderComponent de Angular. Este método todavía es privado/experimental. Sin embargo, es muy probable que este método llegue a Angular. Lars Gyrup Brink Nielsen dio un gran taller sobre esto en InDepthConf.

Eso es todo lo que se necesita para cargar un componente usando lazy load.

Una vez que se hizo clic en el botón de inicio, cargamos nuestro componente usando lazy load. Si abrimos la pestaña de red, podemos ver que el fragmento quiz-card-quiz-card-component.js a sido cargado mediante lazy load. En la aplicación en ejecución, se muestra el componente y se muestra la Pregunta.

Agreguemos material 👷

Actualmente, cargamos nuestro QuizCardComponent mediante lazy load. Muy genial. Pero nuestra aplicación aún no es útil.

Cambiemos eso agregando características adicionales y algunos componentes de Angular material.

Incluimos algunos hermosos componentes de Material. Pero, ¿dónde agregamos los módulos de material?

Sí, podríamos agregarlos a nuestro AppModule. Pero, esto significa que esos módulos se cargan ansiosamente (eagerly loaded). Entonces esa no es la mejor solución. Además, nuestra compilación falla 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.
Enter fullscreen mode Exit fullscreen mode

¿Ahora que? Como puedes imaginar, existe una solución a este problema. ¡Y la respuesta son módulos!
Pero esta vez los usaremos de manera ligeramente distinta. Agregamos un pequeño módulo al mismo archivo que nuestro QuizCardComponent.

Esta especificación de módulo solo pertenece a nuestro componente lazy loaded. Por lo tanto, el único componente que este módulo declarará es el QuizCardComponent. En la sección de imports, solo agregamos los Módulos necesarios para nuestro componente.
Para asegurarnos de que un módulo cargado ansiosamente (eagerly loaded) no pueda importar el módulo, no lo exportamos.
Volvamos a ejecutar nuestra aplicación y veamos cómo se comporta cuando hacemos clic en el botón "Start Quiz".

¡Increíble! Nuestro QuizCardComponent se carga de manera lazy loaded y se agrega al ViewContainer. También trae todas las dependencias necesarias.

Usemos una herramienta llamada webpack-bundle-analyzer y analicemos el aspecto del paquete.

webpack-bundle-analyzer es un módulo npm que le permite visualizar el tamaño de los archivos de salida del paquete web con un mapa de árbol interactivo con zoom.

El tamaño de nuestro paquete principal es de alrededor de 260 KB. Si cargáramos de manera ansiosamente (eagerly loaded) el, QuizCardComponent sería de alrededor de 270 KB. Ahorramos alrededor de 10 KB cargando de forma diferida solo este componente. ¡Muy genial!

Nuestro QuizCardComponent se incluyó en un bloque separado. Si echamos un vistazo más de cerca al contenido de este fragmento, no solo encontramos nuestro código QuizCardComponent, sino que también vemos los módulos Material utilizados dentro del QuizCardComponent.

Aunque QuizCardComponent usó MatButtonModule y MatCardModule, solo MatCardModule termina en el fragmento del componente de la tarjeta de prueba. La razón de esto es porque también usamos MatButtonModule en nuestro AppModule para mostrar el botón de inicio de prueba. Por lo tanto, termina en otro trozo.

En este punto, cargamos de manera lazy-loaded nuestro QuizCardComponent, que muestra una hermosa tarjeta de Material con una imagen y algunas posibles respuestas. Pero, ¿sucede actualmente si hace clic en una de esas posibles respuestas?

Según su respuesta, el botón se vuelve verde o rojo. ¿Pero además de eso? ¡Nada! Entonces ahora se muestra otra pregunta. Arreglemos eso.

Reaccionar ante eventos de componentes con lazy loading

No se muestran más preguntas porque aún no reaccionamos al evento de salida de nuestro componente lazy-loaded. Ya sabemos que nuestro QuizCardComponent emite eventos usando un EventEmitter. Si miramos la definición de clase de EventEmitter, podemos ver que EventEmitter hereda de Subject.

export declara la clase EventEmitter <T extiende cualquiera> extiende Subject <T>
Enter fullscreen mode Exit fullscreen mode

Significa que EventEmitter también tiene un método de suscripción, que nos permite reaccionar a los eventos emitidos.

Nos suscribimos al flujo questionAnswered y llamamos al método showNextQuestion, que luego ejecuta nuestra lógica lazyLoadQuizCard.

takeUntil(instance.destroy$) es necesario para limpiar la suscripción una vez que el componente se destruya. Si se llama al gancho del ciclo de vida (lifecycle hook) ngOnDestroy de QuizCardComponent, se llama a destroy$ el Subject es llamado con next y complete.

async showNewQuestion() {
  this.lazyLoadQuizCard();
}
Enter fullscreen mode Exit fullscreen mode

Dado que la QuizCard ya se cargó, no se ha realizado ninguna solicitud HTTP adicional. Usamos el contenido del fragmento cargado previamente, creamos un nuevo componente y lo agregamos a nuestro contenedor.

Ganchos de ciclo de vida(Life cycle hooks)

Casi todos los enganches del ciclo de vida se llaman automáticamente si cargamos de manera lazy load nuestro QuizCardComponent. Pero falta un gancho, ¿ves cuál?

Es el primero de todos los ganchos, ngOnChanges. Dado que actualizamos manualmente las propiedades de entrada de nuestra instancia de componente, también somos responsables de llamar al enlace del ciclo de vida ngOnChanges.

Para llamar a ngOnChanges en la instancia, necesitamos construir manualmente el object SimpleChanges.

Llamamos manualmente a ngOnChanges en nuestra instancia de componente y le pasamos un objeto SimpleChange. El SimpleChange indica que es el primer cambio, que el valor anterior era null y que el valor actual es nuestra pregunta.

¡Increíble! Cargamos de forma diferida un componente con módulos de terceros, reaccionamos a los eventos de salida y llamamos a los enlaces correctos del gancho ciclo de vida(life cycle hooks).

¿Te Interesa el código fuente?

Todas las fuentes utilizadas a lo largo de esta publicación de blog están disponibles públicamente en el siguiente repositorio.

https://github.com/kreuzerk/city-quiz

Conclusión

El componente mediante Lazy loading ofrece grandes posibilidades para optimizar aún más nuestra aplicación en lo que respecta al rendimiento. Tenemos un control más granular de lo que queremos cargar en forma diferida en comparación con las funciones de carga diferida con el enrutador angular.

Desafortunadamente, todavía necesitamos módulos cuando usamos otros módulos en nuestro componente. Tenga en cuenta que es muy probable que esto cambie en el futuro.

Ivy usa la localidad, lo que permite que los componentes vivan por sí mismos. Este cambio es la base para el futuro de Angular.

🧞‍ 🙏 Si te gustó esta publicación, compártela y aplaude👏🏻 haciendo clic varias veces en el botón de aplaudir en el lado izquierdo.

Los aplausos ayudan a otras personas a descubrir contenido y me motivan a traducir más artículos 😉

Ng-sorting

https://www.npmjs.com/package/ng-sortgrid

Muchísimas gracias a Lars Gyrup Brink Nielsen y al autor Kevin Kreuzer de esta maravilloso articulo, ahora muchos artículos de Angular estarán en español.

Articulo original si lo deseas ver en ingles 
https://medium.com/angular-in-depth/lazy-load-components-in-angular-596357ab05d8

💖 💪 🙅 🚩
antoniocardenas
Antonio Cardenas

Posted on October 29, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Usar Lazy load en componentes Angular
angular Usar Lazy load en componentes Angular

October 29, 2020