├── .gitignore
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
├── app
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── lazy
│ │ ├── index.ts
│ │ ├── lazy.component.ts
│ │ ├── lazy.module.ts
│ │ └── lazy.service.ts
│ ├── module1
│ │ ├── m1.module.ts
│ │ ├── m1comp1.component.ts
│ │ └── m1comp2.component.ts
│ └── module2
│ │ ├── m2.module.ts
│ │ ├── m2comp1.component.ts
│ │ ├── m2comp2.component.ts
│ │ └── m2comp3.component.ts
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
└── tsconfig.app.json
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events*.json
15 | speed-measure-plugin*.json
16 |
17 | # IDEs and editors
18 | /.idea
19 | .project
20 | .classpath
21 | .c9/
22 | *.launch
23 | .settings/
24 | *.sublime-workspace
25 |
26 | # IDE - VSCode
27 | .vscode/*
28 | !.vscode/settings.json
29 | !.vscode/tasks.json
30 | !.vscode/launch.json
31 | !.vscode/extensions.json
32 | .history/*
33 |
34 | # misc
35 | /.sass-cache
36 | /connect.lock
37 | /coverage
38 | /libpeerconnection.log
39 | npm-debug.log
40 | yarn-error.log
41 | testem.log
42 | /typings
43 |
44 | # System Files
45 | .DS_Store
46 | Thumbs.db
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # lazy-load-ng-modules
2 |
3 | A demo of how to lazy load modules with Angular Ivy.
4 |
5 | Even though you can lazy load a component without a module, sometimes you need to load the module, too. This is how it's done with Ivy.
6 |
7 | "Load component" shows how to create a component, knowing its type.
8 |
9 | The rest is a proof of concept "module exploer" that lazy loads a module or the other, displays its components, which can be instantiated.
10 |
11 | For an explanation of how this works read [Lazy loading Angular modules with Ivy](https://angular.love/lazy-loading-angular-modules-with-ivy)
12 |
13 | **NOTE**: As noted in the mentioned article, the "module explorer" does not work when built with the `--prod` flag.
14 |
15 | [Edit on StackBlitz ⚡️](https://stackblitz.com/edit/lazy-load-ng-modules)
16 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "demo": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "prefix": "app",
11 | "schematics": {},
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "aot": true,
17 | "outputPath": "dist/demo",
18 | "index": "src/index.html",
19 | "main": "src/main.ts",
20 | "polyfills": "src/polyfills.ts",
21 | "tsConfig": "src/tsconfig.app.json",
22 | "assets": [
23 | "src/favicon.ico",
24 | "src/assets"
25 | ],
26 | "styles": [
27 | "src/styles.css"
28 | ],
29 | "scripts": []
30 | },
31 | "configurations": {
32 | "production": {
33 | "fileReplacements": [
34 | {
35 | "replace": "src/environments/environment.ts",
36 | "with": "src/environments/environment.prod.ts"
37 | }
38 | ],
39 | "optimization": true,
40 | "outputHashing": "all",
41 | "sourceMap": false,
42 | "extractCss": true,
43 | "namedChunks": false,
44 | "aot": true,
45 | "extractLicenses": true,
46 | "vendorChunk": false,
47 | "buildOptimizer": true
48 | }
49 | }
50 | },
51 | "serve": {
52 | "builder": "@angular-devkit/build-angular:dev-server",
53 | "options": {
54 | "browserTarget": "demo:build"
55 | },
56 | "configurations": {
57 | "production": {
58 | "browserTarget": "demo:build:production"
59 | }
60 | }
61 | },
62 | "extract-i18n": {
63 | "builder": "@angular-devkit/build-angular:extract-i18n",
64 | "options": {
65 | "browserTarget": "demo:build"
66 | }
67 | },
68 | "test": {
69 | "builder": "@angular-devkit/build-angular:karma",
70 | "options": {
71 | "main": "src/test.ts",
72 | "polyfills": "src/polyfills.ts",
73 | "tsConfig": "src/tsconfig.spec.json",
74 | "karmaConfig": "src/karma.conf.js",
75 | "styles": [
76 | "styles.css"
77 | ],
78 | "scripts": [],
79 | "assets": [
80 | "src/favicon.ico",
81 | "src/assets"
82 | ]
83 | }
84 | },
85 | "lint": {
86 | "builder": "@angular-devkit/build-angular:tslint",
87 | "options": {
88 | "tsConfig": [
89 | "src/tsconfig.app.json",
90 | "src/tsconfig.spec.json"
91 | ],
92 | "exclude": [
93 | "**/node_modules/**"
94 | ]
95 | }
96 | }
97 | }
98 | }
99 | },
100 | "defaultProject": "demo"
101 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lazy-load-ng-modules",
3 | "version": "0.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "@angular/common": "~9.0.7",
7 | "@angular/compiler": "~9.0.7",
8 | "@angular/core": "~9.0.7",
9 | "@angular/forms": "~9.0.7",
10 | "@angular/platform-browser": "~9.0.7",
11 | "@angular/platform-browser-dynamic": "~9.0.7",
12 | "@angular/router": "~9.0.7",
13 | "core-js": "2",
14 | "rxjs": "~6.5.4",
15 | "tslib": "^1.10.0",
16 | "zone.js": "^0.10.2"
17 | },
18 | "scripts": {
19 | "ng": "ng",
20 | "start": "ng serve",
21 | "build": "ng build",
22 | "test": "ng test",
23 | "lint": "ng lint",
24 | "e2e": "ng e2e"
25 | },
26 | "devDependencies": {
27 | "@angular-devkit/build-angular": "~14.2.9",
28 | "@angular/cli": "~15.1.4",
29 | "@angular/compiler-cli": "~9.0.7",
30 | "@angular/language-service": "~9.0.7",
31 | "@types/jasmine": "~2.8.8",
32 | "@types/jasminewd2": "~2.0.3",
33 | "@types/node": "~8.9.4",
34 | "codelyzer": "^5.1.2",
35 | "jasmine-core": "~2.99.1",
36 | "jasmine-spec-reporter": "~4.2.1",
37 | "karma": "^6.3.16",
38 | "karma-chrome-launcher": "~2.2.0",
39 | "karma-coverage-istanbul-reporter": "~2.0.1",
40 | "karma-jasmine": "~1.1.2",
41 | "karma-jasmine-html-reporter": "^0.2.2",
42 | "protractor": "~5.4.0",
43 | "ts-node": "~8.3.0",
44 | "tslint": "~5.18.0",
45 | "typescript": "~3.7.5"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | flex-flow: column;
4 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
5 | }
6 |
7 | .buttons {
8 | display: flex;
9 | flex-flow: row;
10 | justify-content: space-evenly;
11 | padding: 0.5em;
12 | }
13 |
14 | .contents {
15 | display: flex;
16 | flex-flow: row;
17 | background-color: rgb(230, 222, 213);
18 | height: 300px;
19 | }
20 |
21 | .anchor:not(:empty) {
22 | padding: 0.5em;
23 | border: dashed 1px rgb(83, 83, 83);
24 | }
25 |
26 | ul {
27 | margin: 0;
28 | padding: 0;
29 | list-style-type: none;
30 | }
31 |
32 | li {
33 | background:rgb(12, 88, 158);
34 | color: rgb(237, 243, 248);
35 | padding: 0.5em;
36 | cursor: pointer;
37 | }
38 |
39 | li + li {
40 | border-top: solid 1px rgb(224, 222, 222);
41 | }
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | - {{factory.componentType.name}}
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Compiler, Component, ComponentFactory, Injector, NgModuleFactory, Type, ViewChild, ViewContainerRef } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'my-app',
5 | templateUrl: './app.component.html',
6 | styleUrls: [ './app.component.css' ]
7 | })
8 | export class AppComponent {
9 | @ViewChild('anchor', { read: ViewContainerRef }) anchor: ViewContainerRef;
10 |
11 | componentFactories: ComponentFactory[];
12 |
13 | constructor(private compiler: Compiler, private injector: Injector) { }
14 |
15 | async loadComponent() {
16 | this.componentFactories = [];
17 | const { LazyModule, LazyComponent } = await import('./lazy');
18 | const moduleFactory = LazyModule instanceof NgModuleFactory ? LazyModule
19 | : (await this.compiler.compileModuleAsync(LazyModule));
20 | const moduleRef = moduleFactory.create(this.injector);
21 | const componentFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(LazyComponent);
22 | this.anchor.clear();
23 | this.anchor.createComponent(componentFactory);
24 | }
25 |
26 | async loadModule1() {
27 | this.loadModule(await import('./module1/m1.module').then(m => m.Module1));
28 | }
29 |
30 | async loadModule2() {
31 | this.loadModule(await import('./module2/m2.module').then(m => m.Module2));
32 | }
33 |
34 | createComponent(factory: ComponentFactory) {
35 | this.anchor.clear();
36 | this.anchor.createComponent(factory);
37 | }
38 |
39 | private loadModule(moduleType: Type) {
40 | this.anchor.clear();
41 | const moduleFactories = this.compiler.compileModuleAndAllComponentsSync(moduleType);
42 | this.componentFactories = moduleFactories.componentFactories;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | import { AppComponent } from './app.component';
6 |
7 | @NgModule({
8 | imports: [ BrowserModule ],
9 | declarations: [ AppComponent ],
10 | bootstrap: [ AppComponent ]
11 | })
12 | export class AppModule { }
13 |
--------------------------------------------------------------------------------
/src/app/lazy/index.ts:
--------------------------------------------------------------------------------
1 | export { LazyModule } from './lazy.module';
2 | export { LazyComponent } from './lazy.component';
3 |
--------------------------------------------------------------------------------
/src/app/lazy/lazy.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { LazyService } from './lazy.service';
3 |
4 | @Component({
5 | selector: 'app-lazy',
6 | template: `
7 | This is a lazy component with an ngFor:
8 |
9 | {{service.value}}`
10 | })
11 | export class LazyComponent {
12 | items = ['Item 1', 'Item 2', 'Item 3'];
13 |
14 | constructor(public service: LazyService) { }
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/lazy/lazy.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 | import { LazyComponent } from './lazy.component';
4 | import { LazyService } from './lazy.service';
5 |
6 | @NgModule({
7 | declarations: [LazyComponent],
8 | imports: [CommonModule],
9 | providers: [LazyService]
10 | })
11 | export class LazyModule { }
12 |
--------------------------------------------------------------------------------
/src/app/lazy/lazy.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 |
3 | @Injectable()
4 | export class LazyService {
5 | value = 'I am lazy';
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/module1/m1.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import {M1Comp1} from './m1comp1.component';
3 | import {M1Comp2} from './m1comp2.component';
4 |
5 | @NgModule({
6 | declarations: [M1Comp1, M1Comp2]
7 | })
8 | export class Module1 {}
--------------------------------------------------------------------------------
/src/app/module1/m1comp1.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 |
3 | @Component({
4 | template: `This is component 1 from module 1`
5 | })
6 | export class M1Comp1 { }
--------------------------------------------------------------------------------
/src/app/module1/m1comp2.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 |
3 | @Component({
4 | template: `This is component 2 from module 1`
5 | })
6 | export class M1Comp2 { }
--------------------------------------------------------------------------------
/src/app/module2/m2.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import {M2Comp1} from './m2comp1.component';
3 | import {M2Comp2} from './m2comp2.component';
4 | import {M2Comp3} from './m2comp3.component';
5 |
6 | @NgModule({
7 | declarations: [M2Comp1, M2Comp2, M2Comp3]
8 | })
9 | export class Module2 {}
--------------------------------------------------------------------------------
/src/app/module2/m2comp1.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 |
3 | @Component({
4 | template: `This is component 1 from module 2`
5 | })
6 | export class M2Comp1 { }
--------------------------------------------------------------------------------
/src/app/module2/m2comp2.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 |
3 | @Component({
4 | template: `This is component 2 from module 2`
5 | })
6 | export class M2Comp2 { }
--------------------------------------------------------------------------------
/src/app/module2/m2comp3.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 |
3 | @Component({
4 | template: `This is component 3 from module 2`
5 | })
6 | export class M2Comp3 { }
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: false
3 | };
4 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 | loading
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import './polyfills';
2 |
3 | import { enableProdMode } from '@angular/core';
4 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
5 |
6 | import { AppModule } from './app/app.module';
7 | import { environment } from './environments/environment';
8 |
9 | if (environment.production) {
10 | enableProdMode();
11 | }
12 |
13 | platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
14 | // Ensure Angular destroys itself on hot reloads.
15 | if (window['ngRef']) {
16 | window['ngRef'].destroy();
17 | }
18 | window['ngRef'] = ref;
19 |
20 | // Otherwise, log the boot error
21 | }).catch(err => console.error(err));
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/set';
35 |
36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
37 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
38 |
39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */
40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
41 |
42 |
43 | /** Evergreen browsers require these. **/
44 | // import 'core-js/es6/reflect';
45 | // import 'core-js/es7/reflect';
46 |
47 |
48 | /**
49 | * Web Animations `@angular/platform-browser/animations`
50 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
51 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
52 | */
53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
54 |
55 |
56 |
57 | /***************************************************************************************************
58 | * Zone JS is required by Angular itself.
59 | */
60 | import 'zone.js/dist/zone'; // Included with Angular CLI.
61 |
62 |
63 | /***************************************************************************************************
64 | * APPLICATION IMPORTS
65 | */
66 |
67 | /**
68 | * Date, currency, decimal and percent pipes.
69 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
70 | */
71 | // import 'intl'; // Run `npm install --save intl`.
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /* Add application styles & imports to this file! */
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "types": []
6 | },
7 | "files": [
8 | "main.ts",
9 | "polyfills.ts"
10 | ],
11 | "include": [
12 | "**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "outDir": "./dist/out-tsc",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "downlevelIteration": true,
9 | "experimentalDecorators": true,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "importHelpers": true,
13 | "target": "es2015",
14 | "typeRoots": [
15 | "node_modules/@types"
16 | ],
17 | "lib": [
18 | "es2018",
19 | "dom"
20 | ]
21 | },
22 | "angularCompilerOptions": {
23 | "enableIvy": true,
24 | "fullTemplateTypeCheck": true,
25 | "strictInjectionParameters": true
26 | }
27 | }
--------------------------------------------------------------------------------