DEV Community

Obinna Ogbonna
Obinna Ogbonna

Posted on

Angular Preloading

Have you ever been in a situation where you needed to call an API to return data needed in a particular component and its HTML structure?
And then, you're not certain if that api has valid data at the moment?

If you have been in this situation multiple times like I have been, then you'll very much appreciate angular Preload feature.

Preloading feature in summary, is to enable lazy loaded modules to be able to fetch data needed for its component functions at the point of routing.

Preloading improves user experience a lot, as it can load all data needed for a module or component at the point where you hit the route, and gracefully handle situations where data needed for such component is not available.

Below is a code snippet to preload all lazy-loaded modules in your route:

    import { PreloadAllModules } from '@angular/router';

    const appRoutes: Routes = [
        ...
    ]

    RouterModule.forRoot(
        appRoutes,
        {
            preloadingStrategy: PreloadAllModules
        }
    )

This is an explanation on how preloading works.

Custom Preloading

Most times, you and I know that we do not want ALL lazy loaded module to be preloaded, as it may affect performance. We only want some feature modules to be preloaded. Here is how you can do that...

First thing to do is to create a service that will implement your preloading strategy. Let's call the class SelectivePrelodingStrategyService


    import { Injectable } from '@angular/core';
    import { PreloadingStrategy, Route } from '@angular/router';
    import { Observable, of } from 'rxjs';

    @Injectable({
    providedIn: 'root',
    })
    export class SelectivePreloadingStrategyService implements PreloadingStrategy {

        preload(route: Route, load: () => Observable<any>): Observable<any> {
            if (route.data && route.data['preload']) {
            // log the route path to the console
            console.log('Preloaded: ' + route.path);

            return load();
            } else {
            return of(null);
            }
        }
    }

Then, on every route that you want to be preloaded, add this code:::


    {
        path: 'books',
        loadChildren: () => import('./books/books.module').then(m => m.BooksModule),
        data: { preload: true }
    }

Finally, on the routing module, replace the preloading strategy with the service created above

    import { PreloadAllModules } from '@angular/router';
    import { SelectivePreloadingStrategyService } from './selective-preloading-strategy.service';

    const appRoutes: Routes = [
        ...
    ]

    RouterModule.forRoot(
        appRoutes,
        {
            preloadingStrategy: SelectivePreloadingStrategyService
        }
    )

Prefetching Component Data

You can pre-fetch data from the server using a resolver so it's ready the moment the route is activated. This will also allow to handle errors before routing to the component. There is no point routing to a component where the data required is not available, or showing a blank component while waiting for the data to load from APIs.

To do this, we create a resolverservice. we call it BookDetailResolverService


    import { Injectable }             from '@angular/core';
    import {
    Router, Resolve,
    RouterStateSnapshot,
    ActivatedRouteSnapshot
    }                                 from '@angular/router';
    import { Observable, of, EMPTY }  from 'rxjs';
    import { mergeMap, take }         from 'rxjs/operators';

    import { BookService }  from './book.service';
    import { Book } from './book';

    @Injectable({
    providedIn: 'root',
    })
    export class BookDetailResolverService implements Resolve<Book> {
        constructor(private cs: BookService, private router: Router) {}

        resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Book> | Observable<never> {
            let id = route.paramMap.get('id');

            return this.cs.getBook(id).pipe(
            take(1),
            mergeMap(book => {
                if (book) {
                return of(book);
                } else { // id not found
                this.router.navigate(['/book-center']);
                return EMPTY;
                }
            })
            );
        }
    }


Then, we import this resolve into the routing module and add a resolve object to the detail component route configuration


    import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { BookHomeComponent } from './book-home/book-home.component';

import { BookDetailComponent }     from './book-detail/book-detail.component';

import { CanDeactivateGuard }             from '../can-deactivate.guard';
import { BookDetailResolverService }    from './book-detail-resolver.service';

const bookRoutes: Routes = [
    ...
  {
      path: ':id',
            component: BookDetailComponent,
            canDeactivate: [CanDeactivateGuard],
            resolve: {
              book: BookDetailResolverService
            }
  },
  {
    path: '',
    component: BookHomeComponent
  }
];

@NgModule({
  imports: [
    RouterModule.forChild(bookRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class BookRoutingModule { }

Finally, the BookDetailComponent can fetch the book details from the route data as shown below...


    ngOnInit() {
        this.route.data
            .subscribe((data: { book: Book }) => {
            this.editName = data.book.name;
            this.book = data.book;
            });
    }

Top comments (6)

Collapse
 
dimaostrov profile image
Dmitriy Ostrovskiy

Thanks for the good information!

Collapse
 
obinnaogbonnajoseph profile image
Obinna Ogbonna

You are welcome!

Collapse
 
sebastiandg7 profile image
Sebastián Duque G

Thanks for sharing! Very helpful knowledge

Collapse
 
obinnaogbonnajoseph profile image
Obinna Ogbonna

Glad you found it helpful. You are welcome

Collapse
 
jonhan17hansel profile image
Hansel Jonathan

Nice one!

Collapse
 
obinnaogbonnajoseph profile image
Obinna Ogbonna

Thanks!