Angular Preloading
Tags: angular, html, css, productivity
Published: 2020-04-19 10:36:49

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](https://angular.io/guide/router#lazy-loading-route-configuration) 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:

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

    const appRoutes: Routes = [
        ...
    ]

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

```

[This](https://angular.io/guide/router#how-preloading-works) 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**

```TypeScript

    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:::


```TypeScript

    {
        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

```TypeScript
    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**

```TypeScript

    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

```TypeScript

    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...

```TypeScript

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

```