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)
Thanks for the good information!
You are welcome!
Thanks for sharing! Very helpful knowledge
Glad you found it helpful. You are welcome
Nice one!
Thanks!