Add pre-loading of lazy-load modules in Angular 6.1.9. · nddashti/JavaScript-Demos@0e2ba71 · GitHub
Skip to content

Commit 0e2ba71

Browse files
committed
Add pre-loading of lazy-load modules in Angular 6.1.9.
1 parent 57ee116 commit 0e2ba71

57 files changed

Lines changed: 10606 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 0 deletions
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
# Now that we're using Webpack, we can install modules locally and just ignore
3+
# them since the assets are baked into the compiled modules.
4+
node_modules/
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
2+
// Import the core angular services.
3+
// import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
4+
import { BrowserModule } from "@angular/platform-browser";
5+
import { NgModule } from "@angular/core";
6+
import { PreloadAllModules } from "@angular/router";
7+
import { RouterModule } from "@angular/router";
8+
import { Routes } from "@angular/router";
9+
10+
// Import the application components and services.
11+
import { AppViewComponent } from "./views/app-view.component";
12+
import { AsideView } from "./views/aside-view/aside-view.module";
13+
import { FeatureAView } from "./views/feature-a-view/feature-a-view.module";
14+
import { FeatureBView } from "./views/feature-b-view/feature-b-view.module";
15+
import { FeatureCView } from "./views/feature-c-view/feature-c-view.module";
16+
17+
// ----------------------------------------------------------------------------------- //
18+
// ----------------------------------------------------------------------------------- //
19+
20+
// When we included a routable view into the router tree, we have to define the routes
21+
// and, SOMETIMES, import the view modules (when statically loaded). In order to keep
22+
// the routing semantics consistent across our views, I'm pushing both the ROUTE and
23+
// MODULE definitions into the subview. This way, the parent context always SPREADS both
24+
// the modules (into the imports) and the routes (into the RouterModule) into its own
25+
// definition. This allows a module to switch from statically loaded to lazy loaded
26+
// without the parent context having to know about it.
27+
export interface RoutableView {
28+
modules: any[],
29+
routes: Routes
30+
}
31+
32+
@NgModule({
33+
imports: [
34+
BrowserModule,
35+
// NOTE: When a routing module is statically included, then the routing module
36+
// needs to be explicitly imported. In order to not worry about this divergence,
37+
// let's let the child module define the importable modules (which may or may
38+
// not be an EMPTY ARRAY - empty if lazy-loaded).
39+
// --
40+
...AsideView.modules, // <--- empty array.
41+
...FeatureAView.modules, // <--- empty array.
42+
...FeatureBView.modules, // <--- empty array.
43+
...FeatureCView.modules,
44+
// --
45+
RouterModule.forRoot(
46+
[
47+
{
48+
path: "app",
49+
children: [
50+
// CAUTION: These routes define LAZY LOADED modules.
51+
...FeatureAView.routes, // <--- using "loadChildren"
52+
...FeatureBView.routes, // <--- using "loadChildren"
53+
...AsideView.routes, // <--- using "loadChildren"
54+
55+
// CAUTION: These routes define STATICALLY LOADED modules.
56+
...FeatureCView.routes
57+
]
58+
},
59+
// Handle root redirect to app.
60+
{
61+
path: "",
62+
pathMatch: "full",
63+
redirectTo: "app"
64+
},
65+
// Handle root not-found redirect.
66+
{
67+
path: "**",
68+
redirectTo: "/app"
69+
}
70+
],
71+
{
72+
// Tell the router to use the HashLocationStrategy.
73+
useHash: true,
74+
enableTracing: false,
75+
76+
// This will tell Angular to preload the lazy-loaded routes after the
77+
// application has been bootstrapped. This will extend to both top-level
78+
// and nested lazy-loaded modules.
79+
preloadingStrategy: PreloadAllModules
80+
}
81+
)
82+
],
83+
providers: [
84+
// CAUTION: We don't need to specify the LocationStrategy because we are setting
85+
// the "useHash" property in the Router module above (which will be setting the
86+
// strategy for us).
87+
// --
88+
// {
89+
// provide: LocationStrategy,
90+
// useClass: HashLocationStrategy
91+
// }
92+
],
93+
declarations: [
94+
AppViewComponent
95+
],
96+
bootstrap: [
97+
AppViewComponent
98+
]
99+
})
100+
export class AppModule {
101+
// ...
102+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
6+
<title>
7+
Preloading Lazy-Loaded Feature Modules In Angular 6.1.9
8+
</title>
9+
</head>
10+
<body>
11+
12+
<h1>
13+
Preloading Lazy-Loaded Feature Modules In Angular 6.1.9
14+
</h1>
15+
16+
<my-app>
17+
<p>
18+
<em>Loading files...</em>
19+
</p>
20+
21+
<p>
22+
npm Run Scripts:
23+
</p>
24+
25+
<ul>
26+
<li>
27+
<strong>npm run build</strong> &mdash; Compiles the .ts file into bundles.
28+
</li>
29+
<li>
30+
<strong>npm run watch</strong> &mdash; Compiles the .ts file into bundles and then watches files for changes.
31+
</li>
32+
</ul>
33+
</my-app>
34+
35+
</body>
36+
</html>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
// Import these libraries for their side-effects.
3+
import "core-js/client/shim.min.js";
4+
import "zone.js/dist/zone.js";
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
// Import for side effects - we have to import this first so that the polyfills will
3+
// be available for the rest of the code.
4+
// --
5+
// NOTE: I would normally include this as an Entry bundle; but, I couldn't get the
6+
// HtmlWebpackPlugin to work properly if I did that (since I don't think it could
7+
// implicitly determine the dependency order). In the future, I might be able to make
8+
// this more dynamic (ie, use Webpack's import() syntax).
9+
import "./main.polyfill";
10+
11+
// ----------------------------------------------------------------------------------- //
12+
// ----------------------------------------------------------------------------------- //
13+
14+
// Import the core angular services.
15+
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
16+
17+
// Import the root module for bootstrapping.
18+
import { AppModule } from "./app.module";
19+
20+
platformBrowserDynamic().bootstrapModule( AppModule );
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
App View
3+
4+
<p>
5+
<a routerLink="/app/">Home</a> &mdash;
6+
<a routerLink="/app/feature-a">Feature A</a> &mdash;
7+
<a routerLink="/app/feature-b">Feature B</a> &mdash;
8+
<a routerLink="/app/feature-c">Feature C</a> &mdash;
9+
<a [routerLink]="[ '/app', { outlets: { aside: 'aside' } } ]">Aside</a>
10+
</p>
11+
12+
<router-outlet></router-outlet>
13+
14+
<router-outlet name="aside"></router-outlet>
15+
16+
<!-- I indicate that a router module is being loaded asynchronously. -->
17+
<div
18+
*ngIf="isShowingRouteLoadIndicator"
19+
class="router-load-indicator">
20+
Loading Module
21+
</div>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
2+
:host {
3+
display: block ;
4+
}
5+
6+
a {
7+
color: red ;
8+
cursor: pointer ;
9+
text-decoration: underline ;
10+
}
11+
12+
// On a fast network connection, the lazy-loaded modules will load almost instantly. In
13+
// such a case, we don't want to bother showing the asynchronous loading indicator as it
14+
// will simply flash the UI and create a distracting user experience. As such, we want to
15+
// put a small delay on the "observability" of the loading indicator. This way, on a fast
16+
// connection, it will be removed before the delay is consumed; and, on a slower network
17+
// connection, it will be shown to the user soon after the asynchronous load starts.
18+
// --
19+
// CAUTION: keyframes are not "protected" by Angular's simulated encapsulation. As such,
20+
// this animation name needs to be universally unique to the application.
21+
@keyframes router-load-indicator-animation {
22+
from {
23+
opacity: 0.0 ;
24+
}
25+
26+
to {
27+
opacity: 1.0 ;
28+
}
29+
}
30+
31+
.router-load-indicator {
32+
// Delay the animation for a fast network connection (so users don't see the loader).
33+
animation-delay: 100ms ;
34+
animation-duration: 200ms ;
35+
animation-fill-mode: both ;
36+
animation-name: router-load-indicator-animation ;
37+
// --
38+
background-color: #ffdc73 ;
39+
border-radius: 5px 5px 5px 5px ;
40+
box-shadow: 0px 2px 2px fade( #000000, 20% ) ;
41+
color: #000000 ;
42+
font-family: monospace ;
43+
font-size: 16px ;
44+
left: 50% ;
45+
padding: 7px 15px 7px 15px ;
46+
position: fixed ;
47+
text-transform: lowercase ;
48+
top: 10px ;
49+
transform: translateX( -50% ) ;
50+
z-index: 2 ;
51+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
2+
// Import the core angular services.
3+
import { Component } from "@angular/core";
4+
import { Event as RouterEvent } from "@angular/router";
5+
import { NavigationCancel } from "@angular/router";
6+
import { NavigationEnd } from "@angular/router";
7+
import { NavigationError } from "@angular/router";
8+
import { NavigationStart } from "@angular/router";
9+
import { Router } from "@angular/router";
10+
import { RouteConfigLoadEnd } from "@angular/router";
11+
import { RouteConfigLoadStart } from "@angular/router";
12+
13+
// ----------------------------------------------------------------------------------- //
14+
// ----------------------------------------------------------------------------------- //
15+
16+
@Component({
17+
selector: "my-app",
18+
styleUrls: [ "./app-view.component.less" ],
19+
templateUrl: "./app-view.component.htm"
20+
})
21+
export class AppViewComponent {
22+
23+
public isShowingRouteLoadIndicator: boolean;
24+
25+
// I initialize the app view component.
26+
constructor( router: Router ) {
27+
28+
this.isShowingRouteLoadIndicator = false;
29+
30+
// *************************************************************************** //
31+
// CAUTION: Even though we are preloading our lazy-loading modules with the
32+
// "PreloadAllModules" configuration, the following Router events still fire
33+
// when the RouterPreloader's requests for the remote code are initiated and
34+
// completed, respectively. As such, this code is still relevant. And, on a
35+
// slower connection, may still show the loading indicator shortly after the
36+
// application has been bootstrapped (depending on what the initial URL of
37+
// the application is).
38+
// *************************************************************************** //
39+
40+
// As the router loads modules asynchronously (via loadChildren), we're going to
41+
// keep track of how many asynchronous requests are currently active. If there is
42+
// at least one pending load request, we'll show the indicator.
43+
var asyncLoadCount = 0;
44+
45+
// As the user navigates around the application, we're going to keep track of how
46+
// many pending navigation requests are currently active. This way, we can know
47+
// if the asynchronous module loading is [possibly] happening because of a user
48+
// navigation; or, if it's happening as part of the pre-loading.
49+
var navigationCount = 0;
50+
51+
// The Router emits special events for "loadChildren" configuration loading. We
52+
// just need to listen for the Start and End events, amidst the rest of the
53+
// events, in order to determine if we have any pending configuration requests.
54+
router.events.subscribe(
55+
( event: RouterEvent ) : void => {
56+
57+
if ( event instanceof RouteConfigLoadStart ) {
58+
59+
asyncLoadCount++;
60+
61+
} else if ( event instanceof RouteConfigLoadEnd ) {
62+
63+
asyncLoadCount--;
64+
65+
} else if ( event instanceof NavigationStart ) {
66+
67+
navigationCount++;
68+
69+
} else if (
70+
( event instanceof NavigationEnd ) ||
71+
( event instanceof NavigationError ) ||
72+
( event instanceof NavigationCancel )
73+
) {
74+
75+
navigationCount--;
76+
77+
}
78+
79+
// If there is at least one pending asynchronous config load request AND
80+
// it is taking place while a the user is actively navigating around the
81+
// application, then let's show the loading indicator. This way, we don't
82+
// show the loading indicator during the preloading of lazy modules. This
83+
// isn't an exact science, since the navigation may not be tied to the
84+
// config load request. But, the small delay in rendering the indicator
85+
// should make the fuzzy-association a non-issue (as unrelated navigation
86+
// events will start and end almost instantly).
87+
// --
88+
// CAUTION: I'm using CSS to include a small delay such that this loading
89+
// indicator won't be seen by people with sufficiently fast connections.
90+
this.isShowingRouteLoadIndicator = !! ( navigationCount && asyncLoadCount );
91+
92+
}
93+
);
94+
95+
}
96+
97+
}
Lines changed: 16 additions & 0 deletions

0 commit comments

Comments
 (0)