Implement lazy loading for the FlightBookingModule
in your app.routes.ts
.
Keep in mind that lazy loading only works if the module in question isn't referenced directly but only with a string in the router configuration.
-
Open the file
app.module.ts
and remove the import for theFlightBookingModule
.Show Code
@NgModule({ imports: [ [...] // FlightBookingModule, // ^^ Removed b/c this would prevent lazy loading [...] ], [...] }) export class AppModule { }
-
Open the file
app.routes.ts
and introduce a route with the pathflight-booking
. It should point to theFlightBookingModule
usingloadChildren
:Show Code
[...] { path: 'flight-booking', loadChildren: () => import('./flight-booking/flight-booking.module').then(m => m.FlightBookingModule) }, { // This route needs to be the last one! path: '**', [...] } [...]
-
Open the file
flight-booking.routes.ts
and change the path for the first route to an empty string (path: ''
) to make this route the default route that is activated after lazy loading the module. Put your other routes (flight-edit and passenger-search) into the children array.Show Code
[...] export const FLIGHT_BOOKING_ROUTES: Routes = [ { path: 'flight-edit/:id', component: FlightEditComponent }, { path: 'flight-search', component: FlightSearchComponent }, { path: 'passenger-search', component: PassengerSearchComponent } ];
-
Make sure your sidebar link to flight-search and passenger-search still works (something like
routerLink="/flight-booking/flight-search"
). -
Also make sure your
Edit
Button in yourFlightCardComponent
still works (try adding two dots like[routerLink]="['../flight-edit', ...
). -
Find out that webpack splits off an own chunk for the
FlightBookingModule
after implementing lazy loading. If this works, you will see another chunk at the console (e. g.flight-booking-flight-booking-module.js
depending on the used version of the CLI) -
Try it out in the browser and use the network tab within the dev tools (F12) to make sure that it is only loaded on demand. If it doesn't work, have a look to the console tab within the dev tools.
In this exercise you will implement Preloading using Angular's PreloadAllModules
strategy.
-
Open the file
app.module.ts
and register thePreloadAllModules
strategy when callingRouterModule.forRoot
.Show Code
RouterModule.forRoot(APP_ROUTES, { preloadingStrategy: PreloadAllModules });
-
Make sure it works using the network tab within Chrome's dev tools. If it works, the lazy bundles are loaded after the app has been initializes. If this is the case, the chunks show up quite late in the water fall diagram.
-
Open the file
flight-search.component.ts
and add this methoddelayFirstFlight
which you bind to the new button with the labelDelay 1st Flight
in the HTML Template.delayFirstFlight(): void { const ONE_MINUTE = 1000 * 60; const oldFlights = this.flights; const oldFlight = oldFlights[0]; const oldDate = new Date(oldFlight.date); // Mutable oldDate.setTime(oldDate.getTime() + 15 * ONE_MINUTE); oldFlight.date = oldDate.toISOString(); }
[...] Search </button> <button *ngIf="flights.length > 0" class="btn btn-default" style="margin-left: 10px" (click)="delayFirstFlight()"> Delay 1st Flight </button> </div> [...]
-
Now open the file
flight-card.component.ts
inject this in your constructor:constructor(private element: ElementRef, private zone: NgZone) {}
(make sure the imports are added correctly) and then add thisblink
method to your component.blink(): void { // Dirty Hack used to visualize the change detector // let originalColor = this.element.nativeElement.firstChild.style.backgroundColor; this.element.nativeElement.firstChild.style.backgroundColor = 'crimson'; // ^----- DOM-Element this.zone.runOutsideAngular(() => { setTimeout(() => { this.element.nativeElement.firstChild.style.backgroundColor = 'white'; }, 1000); }); }
-
Move to the file
flight-card.component.html
and create a data binding for this method at the end:{{ blink() }}
Please note that binding methods is not a good idea with respect to performance. We do it here just to visualize the change tracker.
-
Open the solution in the browser and search for flights form
Hamburg
toGraz
. -
Click the button
Delay 1st Flight
and see that just the first flight gets a new date. But you also see that every component is checked for changes by Angular b/c every component blinks. -
Open the file
flight-card.component.ts
. Switch onOnPush
for yourFlightCard
.Show Code
import {ChangeDetectionStrategy} from '@angular/core'; [...] @Component({ selector: 'flight-card', templateUrl: 'flight-card.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class FlightCardComponent { [...] }
-
Open the
flight-search.component.ts
and alter it to update the selected flight's date in an immutable way:Show Code
delay() { const ONE_MINUTE = 1000 * 60; const oldFlights = this.flights; const oldFlight = oldFlights[0]; const oldDate = new Date(oldFlight.date); // Mutable // oldDate.setTime(oldDate.getTime() + 15 * ONE_MINUTE ); // oldFlight.date = oldDate.toISOString(); // Immutable const newDate = new Date(oldDate.getTime() + 15 * ONE_MINUTE); const newFlight: Flight = { ...oldFlight, date: newDate.toISOString() }; this.flights = [ newFlight, ...oldFlights.slice(1) ]; }
You find some information about the object spread operator (e. g. ...oldFlight
) here (scroll down to Object Spread) and about the array spread operator (e. g. [newFlight, ...oldFlights.slice(1)]) here.
- Make sure your implementation works. Switch to the browser and search for flights again. Click
Delay 1st Flight
one more time and find out that Angular is just checking and updating the first flight card.
-
Make sure, your solution runs in debug mode (
ng serve -o
) -
Open the performance tab in Chrome's dev tools and reload the app. Find out how long bootstrapping takes and create a screenshot.
Hint: In order to respect the cache, do it twice and take the screenshot after the 2nd try.
-
Install the simple web server serve:
npm install serve -g
-
Switch to the console and move to the root folder of your project. Create a production build:
ng build --prod
-
Start live-server for your production build. For this, switch to your project within the
dist
folder and call serve:serve -s
-
Open the performance tab in Chrome's dev tools and reload the app. Find out how long bootstrapping takes and create a screenshot.
Hint: In order to respect the cache, do it twice and take the screenshot after the 2nd try.
-
Compare your screenshot with the performance results.
Using the webpack-bundle-analyzer one can have a look at a bundle's content. In this exercise you will use this possibility by inspecting your AOT-based and your AOT-less production build.
-
Install the
webpack-bundle-analyzer
globally (for the sake of simplicity):npm install -g webpack npm install -g webpack-bundle-analyzer
-
Move to the root folder of your project. Create a Production Build without AOT and generate a statistics file for the analyzer using the
stats-json
flag:ng build --prod --aot=false --build-optimizer=false --stats-json
-
Analyze your bundles:
cd dist/flight-app webpack-bundle-analyzer stats.json
The name of
stats.json
can be slightly different on your machine, e. g.stats-es5.json
orstats-es2015.json
. -
Take a screen shot to document this.
-
Move to the root folder of your project. Create a production build using AOT:
ng build --prod --stats-json
-
Analyze these bundles too and compare it to the former bundles:
cd dist/flight-app webpack-bundle-analyzer stats.json
-
Here you find some information about creating a custom preloading strategy. Have a look at it.
-
Create a custom preloading strategy that only preloads modules that have been marked with the
data
attribute in the router configuration. -
Configure the system to make use of it and test it.