0

I have my code as given below in app.component.html i.e. as soon as the user lands on the home screen; I want to lazy load the app-image-gallery & app-users-list and want to load them with some Shimmer effect or Loader as soon as the user reaches the viewport of that particular component. Almost every grown up site use this but this thing is kind of difficult to find for Angular.

I have read many articles on lazy loading of component on button click but haven't found this thing implemented anywhere.

app.component.html

<app-navbar></<app-navbar>
<app-image-gallery></app-image-gallery> //list of images from backend
<app-users-list></app-users-list>   //users list from backend
<app-faq></app-faq>

UPDATE(2022) - After a lot of reasearch, I found these awesome packages for Angular lazy loading of components because packages like @herodevs/hero-loader & ngx loadable are deprecated for newer versions of angular. I will also attach the link to their articles-

@angular-extensions/elements

@juristr/ngx-lazy-el

ngx-element

Articles-

@juristr/ngx-lazy-el

@angular-extensions/elements

These packages are suitable for Angular 9+ versions. I hope it helps someone. And suppose if you want to load components on scroll, On Scroll Load, this is the thing you are looking for and each solution fully tested.

1 Answers1

4

This solution is been tested with angular 9 and above with ivy.

If you are using older version of angular checkout this article: https://pretagteam.com/question/load-new-modules-dynamically-in-runtime-with-angular-cli-angular-5

Angular 9+ solution:

Before you start you should make sure that your lazy loading components are in a separated module which is not imported in your app module.

First you need to pick a container element and mark it with a template variable to render your lazy component inside it. Something like:

<div #container></div>

And then in your component class you need to query this container using @ViewChild as ViewContainerRef:

@ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;

Now you are ready to lazy load your component using the webpack dynamic import. which as by default available in an angular app.

Then you will need to resolve the component factory using the angular ComponentFactoryResolver after you inject it in your component constructor.

And at the end you will just render the component factory in the view reference you have prepared this.container:

lazyLoadAppImageGallery() {
    import('path/to/your/app-image-gallery-component')
      .then(({AppImageGallery}) => {
          const componentFactory = 
          this.componentFactoryResolver.resolveComponentFactory(AppImageGallery);
          const { instance } = this.container.createComponent(componentFactory);
       });
}

After you have rendered your component you might want to pass some data to it. You could use the instance for this:

instance.propertyName = someValue

Checkout this nice article for more infos: https://medium.com/@ckyidr9/lazy-load-feature-modules-without-routing-in-angular-9-ivy-220851cc7751

Update

Here is a working solution with dynamic render on scroll

https://stackblitz.com/edit/angular-ivy-megwrz

If you might use lazy loading with routes, just checkout angular simple documentation

https://angular.io/guide/lazy-loading-ngmodules

You could also combine both solutions. In case you want to dynamically load a component in a lazy loaded module loaded by route.

Update since Angular v13

Since v13 ComponentFactoryResolver is deprecated. Create component does require resolving component factory. You can use the component class directly thanks to the Ivy engine. Which means instead of writing:

const componentFactory = this.componentFactoryResolver.resolveComponentFactory(AppImageGallery);
      const { instance } = this.container.createComponent(componentFactory);

you could just write:

this.viewContainerRef.createComponent(AppImageGallery);
Mehyar Sawas
  • 1,187
  • 6
  • 15
  • Will this work on landing screen of website so soon as the user reaches the viewport of that specific component then only that component will be loaded and once loaded it will be in cache until the site is opened? – Ashwini Kumar Dec 08 '21 at 16:06
  • This solution will render the component as soon as you call the loader method. You could before check if the user has reached a certain viewport where the component should be rendered and the call this method. I you want more help on how to do this, then please provide more infos an code snippet where this should happen. – Mehyar Sawas Dec 08 '21 at 16:21
  • Yes the component file will be cached in the browser – Mehyar Sawas Dec 08 '21 at 16:24
  • Can you please provide the method to check whether the user reached to that viewport or not, so that I will be able to load the component or any link that can be beneficial to me? – Ashwini Kumar Dec 08 '21 at 16:30
  • Okay let me guess, you mean by reaching the viewport ist when the user scrolls to the point where the component is? If this is not the case so I need to know what you mean by reaching the viewport to be able to suggest an appropriate solution – Mehyar Sawas Dec 08 '21 at 16:39
  • Okay let me guess, you mean by reaching the viewport ist when the user scrolls to the point where the component is? Exactly this one i.e. as soon as the user reaches to the start point of that component – Ashwini Kumar Dec 08 '21 at 16:44
  • https://stackblitz.com/edit/angular-ivy-jyvygl – Mehyar Sawas Dec 08 '21 at 18:01
  • Thanks for your follow up. I am quite close to implement it but the viewport loading of the component is still not clear. Can there be a more efficient way of loading the items as soon as the user reaches the viewport ? I am having 5 components on my home page – Ashwini Kumar Dec 08 '21 at 22:17
  • Well as I have mentioned. If you provide a full picture of your problem code snippet or on stackblitz, you might then get an appropriate an more efficient solution. – Mehyar Sawas Dec 09 '21 at 05:43
  • Here is the attached stackblitz link - https://stackblitz.com/edit/angular-ivy-yrnroz. The data coming in the "product-list.component.ts" is coming from api and apart from that all of the components have static data in my application. And downside of each product, there will be button named "Go to Product details page" after clicking on which I should get the specific product details on a different lazy-loaded route(e.g. /productDetails/:id) -> SHOULD BE A LAZY LOADED CHUNK. – Ashwini Kumar Dec 09 '21 at 12:42
  • When I asked you about what you mean with reaching the viewport you agreed with me on the scroll solution. Now when I look at your explanation and your example, I see that you are looking for the lazy loading route solution. So this is just confusing. Anyway my answer has a solution without routing. And if you have you read angular documentation about lazy loading? https://angular.io/guide/lazy-loading-ngmodules There you will find a solution about the lazy loading concept with routing. Just pick one of this two solutions. – Mehyar Sawas Dec 09 '21 at 20:37
  • I asked you "/productDetails/:id" this as an extra part because as you mentioned in your main answer `instance.propertyName = someValue` this piece of code. So, I thought that there would be separate case of rules for passing the data from the LAZY LOADED COMPONENT as I need to pass `id` on click. And lazy loading of modules, I am already aware of it - angular.io/guide/lazy-loading-ngmodules. Now as I have given you all the circumstances and scenarios for the lazy loading of component, you can edit my stackblitz code - stackblitz.com/edit/angular-ivy-yrnroz and provide me a updated one. – Ashwini Kumar Dec 10 '21 at 06:18
  • So as you can see in the attached stackblitz https://stackblitz.com/edit/angular-ivy-yrnroz, I want to lazy load last 3 components namely **PRODUCTS LIST**, **GET STARTED** & **ABOUT US** i.e. as soon as the user reaches the viewport of that component then only it should be loaded. I hope I have cleared your doubt with these infos. – Ashwini Kumar Dec 10 '21 at 06:32
  • So here is my fork of your project with a working solution https://stackblitz.com/edit/angular-ivy-megwrz I just applied my described solution on your code with some style adjustments – Mehyar Sawas Dec 10 '21 at 14:34
  • Thank you very much **@Mehyar Sawas** for your solution(**Best solution explained so far**). Still there is one minor thing missing; Suppose I have loaded the data of my home page once, I want it to be in cache until I didn't close that specific tab. But in current scenario as soon as you will navigate to the product details page and return back to the landing page, you will lost your loaded data i.e.productListRendered: false, getStartedRendered: false and aboutRendered: false (All three will be false). Basically it's need to be cached. – Ashwini Kumar Dec 10 '21 at 15:06
  • Angular by default does not cache a renderd view or data. But you could easily keep data from components by providing a service on the top level of your app and save the data you want to cache there and restore them onInit. You could also use browser localStorage. – Mehyar Sawas Dec 10 '21 at 17:25
  • No, I am not talking about the caching of the data coming from the backend. I am talking about the **caching of the components** which have been once lazy loaded. – Ashwini Kumar Dec 10 '21 at 17:32
  • You could also use browser localStorage. It is also possible to use route caching strategies. Checkout this article https://medium.com/swlh/how-to-toggle-caching-for-routing-components-in-angular-5a327ea87310 – Mehyar Sawas Dec 10 '21 at 17:34
  • The component file will be cached and will only be loaded once over the network in browser. Next time you use the component after navigation back. The browser does not load the file once again. The local properties set inside the component like aboutLoaded are just saved in the instance angular creates for a one full lifecycle and each time a component get destroyed it will lose its data. However if it is lazy loaded or not. – Mehyar Sawas Dec 10 '21 at 17:42
  • Thanks **@Mehyar Sawas** – Ashwini Kumar Dec 10 '21 at 17:54