├── .gitignore
├── LICENSE
├── README.md
├── angular.json
├── package-lock.json
├── package.json
├── src
├── app
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── backend.service.ts
│ └── lazy-form.component.ts
├── assets
│ └── .gitkeep
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
└── styles.css
├── tsconfig.app.json
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_STORE
2 |
3 | /dist/
4 | /bazel-out
5 | /integration/bazel/bazel-*
6 | *.log
7 | node_modules
8 |
9 | # Include when developing application packages.
10 | pubspec.lock
11 | .c9
12 | .idea/
13 | .devcontainer/*
14 | !.devcontainer/README.md
15 | !.devcontainer/recommended-devcontainer.json
16 | !.devcontainer/recommended-Dockerfile
17 | .settings/
18 | .vscode/launch.json
19 | .vscode/settings.json
20 | .vscode/tasks.json
21 | *.swo
22 | *.swp
23 | modules/.settings
24 | modules/.vscode
25 | .vimrc
26 | .nvimrc
27 |
28 | # Don't check in secret files
29 | *secret.js
30 |
31 | # Ignore npm/yarn debug log
32 | npm-debug.log
33 | yarn-error.log
34 |
35 | # build-analytics
36 | .build-analytics
37 |
38 | # rollup-test output
39 | /modules/rollup-test/dist/
40 |
41 | # User specific bazel settings
42 | .bazelrc.user
43 |
44 | # User specific ng-dev settings
45 | .ng-dev.user*
46 |
47 | .notes.md
48 | baseline.json
49 |
50 | # Ignore .history for the xyz.local-history VSCode extension
51 | .history
52 |
53 | # Husky
54 | .husky/_
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 wittyprogramming
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lazy-load a component in Angular 13 without routing
2 |
3 |
4 | One of the most seeking features in Angular 13 is to lazy load a component when you need it. It is a very straightforward procedure through routing that is well documented. But, what if you do not want to use the router or you want to lazy load a component programmatically through your code?
5 |
6 | Code for the following article:
7 | [https://www.wittyprogramming.dev/articles/lazy-load-component-angular-without-routing/](https://www.wittyprogramming.dev/articles/lazy-load-component-angular-without-routing/)
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "lazy-load-component": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "inlineTemplate": true,
11 | "inlineStyle": true,
12 | "skipTests": true
13 | },
14 | "@schematics/angular:class": {
15 | "skipTests": true
16 | },
17 | "@schematics/angular:directive": {
18 | "skipTests": true
19 | },
20 | "@schematics/angular:guard": {
21 | "skipTests": true
22 | },
23 | "@schematics/angular:interceptor": {
24 | "skipTests": true
25 | },
26 | "@schematics/angular:pipe": {
27 | "skipTests": true
28 | },
29 | "@schematics/angular:service": {
30 | "skipTests": true
31 | },
32 | "@schematics/angular:application": {
33 | "strict": true
34 | }
35 | },
36 | "root": "",
37 | "sourceRoot": "src",
38 | "prefix": "app",
39 | "architect": {
40 | "build": {
41 | "builder": "@angular-devkit/build-angular:browser",
42 | "options": {
43 | "outputPath": "dist/lazy-load-component",
44 | "index": "src/index.html",
45 | "main": "src/main.ts",
46 | "polyfills": "src/polyfills.ts",
47 | "tsConfig": "tsconfig.app.json",
48 | "assets": [
49 | "src/favicon.ico",
50 | "src/assets"
51 | ],
52 | "styles": [
53 | "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
54 | "src/styles.css"
55 | ],
56 | "scripts": []
57 | },
58 | "configurations": {
59 | "production": {
60 | "budgets": [
61 | {
62 | "type": "initial",
63 | "maximumWarning": "500kb",
64 | "maximumError": "1mb"
65 | },
66 | {
67 | "type": "anyComponentStyle",
68 | "maximumWarning": "2kb",
69 | "maximumError": "4kb"
70 | }
71 | ],
72 | "fileReplacements": [
73 | {
74 | "replace": "src/environments/environment.ts",
75 | "with": "src/environments/environment.prod.ts"
76 | }
77 | ],
78 | "outputHashing": "all"
79 | },
80 | "development": {
81 | "buildOptimizer": false,
82 | "optimization": false,
83 | "vendorChunk": true,
84 | "extractLicenses": false,
85 | "sourceMap": true,
86 | "namedChunks": true
87 | }
88 | },
89 | "defaultConfiguration": "production"
90 | },
91 | "serve": {
92 | "builder": "@angular-devkit/build-angular:dev-server",
93 | "configurations": {
94 | "production": {
95 | "browserTarget": "lazy-load-component:build:production"
96 | },
97 | "development": {
98 | "browserTarget": "lazy-load-component:build:development"
99 | }
100 | },
101 | "defaultConfiguration": "development"
102 | },
103 | "extract-i18n": {
104 | "builder": "@angular-devkit/build-angular:extract-i18n",
105 | "options": {
106 | "browserTarget": "lazy-load-component:build"
107 | }
108 | }
109 | }
110 | }
111 | },
112 | "defaultProject": "lazy-load-component"
113 | }
114 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lazy-load-component",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development"
9 | },
10 | "private": true,
11 | "dependencies": {
12 | "@angular/animations": "~13.0.2",
13 | "@angular/cdk": "^13.0.2",
14 | "@angular/common": "~13.0.2",
15 | "@angular/compiler": "~13.0.2",
16 | "@angular/core": "~13.0.2",
17 | "@angular/flex-layout": "^12.0.0-beta.35",
18 | "@angular/forms": "~13.0.2",
19 | "@angular/material": "^13.0.2",
20 | "@angular/platform-browser": "~13.0.2",
21 | "@angular/platform-browser-dynamic": "~13.0.2",
22 | "@angular/router": "~13.0.2",
23 | "rxjs": "~7.4.0",
24 | "tslib": "^2.3.0",
25 | "zone.js": "~0.11.4"
26 | },
27 | "devDependencies": {
28 | "@angular-devkit/build-angular": "~13.0.3",
29 | "@angular/cli": "~13.0.3",
30 | "@angular/compiler-cli": "~13.0.2",
31 | "@types/node": "^16.11.10",
32 | "typescript": "~4.4.4"
33 | }
34 | }
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Component, createNgModuleRef,
3 | Injector,
4 | OnDestroy,
5 | ViewChild,
6 | ViewContainerRef,
7 | } from "@angular/core";
8 | import {Subscription} from "rxjs";
9 |
10 | @Component({
11 | selector: "app-root",
12 | template: `
13 |
14 |
Welcome to lazy loading a Component
15 |
18 |
19 |
20 | `,
21 | styles: [],
22 | })
23 | export class AppComponent implements OnDestroy {
24 | @ViewChild("formComponent", {read: ViewContainerRef})
25 | formComponent!: ViewContainerRef;
26 | formSubmittedSubscription = new Subscription();
27 |
28 | constructor(private injector: Injector) {
29 | }
30 |
31 | async loadForm() {
32 | const {LazyFormModule, LazyFormComponent} = await import("./lazy-form.component");
33 | const moduleRef = createNgModuleRef(LazyFormModule, this.injector);
34 | this.formComponent.clear();
35 | const {instance} = this.formComponent.createComponent(LazyFormComponent, {ngModuleRef: moduleRef});
36 | instance.buttonTitle = "Contact Us";
37 | this.formSubmittedSubscription = instance.formSubmitted.subscribe(() =>
38 | console.log("The Form Submit Event is captured!")
39 | );
40 | }
41 |
42 | ngOnDestroy(): void {
43 | this.formSubmittedSubscription.unsubscribe();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from "@angular/core";
2 | import {BrowserModule} from "@angular/platform-browser";
3 |
4 | import {AppComponent} from "./app.component";
5 | import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
6 | import {MatButtonModule} from "@angular/material/button";
7 |
8 | @NgModule({
9 | declarations: [AppComponent],
10 | imports: [BrowserModule, MatButtonModule, BrowserAnimationsModule],
11 | providers: [],
12 | bootstrap: [AppComponent],
13 | })
14 | export class AppModule {
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/backend.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 |
3 | @Injectable()
4 | export class BackendService {
5 |
6 | constructor() {
7 | }
8 |
9 | submitForm() {
10 | console.log("Form Submitted")
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/lazy-form.component.ts:
--------------------------------------------------------------------------------
1 | import {MatInputModule} from "@angular/material/input";
2 | import {MatFormFieldModule} from "@angular/material/form-field";
3 | import {
4 | Component,
5 | EventEmitter, Input,
6 | NgModule,
7 | OnInit, Output,
8 | } from "@angular/core";
9 | import {
10 | FormControl,
11 | FormGroup,
12 | ReactiveFormsModule,
13 | Validators,
14 | } from "@angular/forms";
15 | import {FlexLayoutModule} from "@angular/flex-layout";
16 | import {MatButtonModule} from "@angular/material/button";
17 | import {BackendService} from "./backend.service";
18 | import {CommonModule} from "@angular/common";
19 |
20 | @Component({
21 | selector: "app-lazy-form",
22 | template: `
23 |
52 | `,
53 | styles: [],
54 | })
55 | export class LazyFormComponent implements OnInit {
56 | @Input()
57 | buttonTitle: string = "Submit";
58 |
59 | @Output() formSubmitted = new EventEmitter();
60 |
61 | simpleForm = new FormGroup({
62 | email: new FormControl("", [Validators.required, Validators.email]),
63 | name: new FormControl("", [Validators.required]),
64 | });
65 |
66 | get name() {
67 | return this.simpleForm.get("name");
68 | }
69 |
70 | get email() {
71 | return this.simpleForm.get("email");
72 | }
73 |
74 | constructor(private backendService: BackendService) {
75 | }
76 |
77 | submitForm() {
78 | if (this.email?.invalid || this.name?.invalid) return;
79 | this.backendService.submitForm();
80 | this.formSubmitted.emit();
81 | alert("Form submitted successfully");
82 | }
83 |
84 | ngOnInit(): void {
85 | }
86 |
87 | getNameErrorMessage() {
88 | if (this.name?.hasError("required")) {
89 | return "You must enter a value";
90 | }
91 |
92 | return this.email?.hasError("email") ? "Not a valid email" : "";
93 | }
94 |
95 | getEmailErrorMessage() {
96 | if (this.email?.hasError("required")) {
97 | return "You must enter a value";
98 | }
99 |
100 | return this.email?.hasError("email") ? "Not a valid email" : "";
101 | }
102 | }
103 |
104 | @NgModule({
105 | declarations: [LazyFormComponent],
106 | imports: [
107 | ReactiveFormsModule,
108 | MatFormFieldModule,
109 | MatInputModule,
110 | CommonModule,
111 | FlexLayoutModule,
112 | MatButtonModule,
113 | ],
114 | providers: [BackendService],
115 | bootstrap: [LazyFormComponent],
116 | })
117 | export class LazyFormModule {
118 | constructor() {
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wittyprogramming/lazy-load-component-angular13/29907e354a6a752771be09696b24e6b7789f7fdd/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wittyprogramming/lazy-load-component-angular13/29907e354a6a752771be09696b24e6b7789f7fdd/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LazyLoadComponent
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.error(err));
13 |
--------------------------------------------------------------------------------
/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/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /**
22 | * By default, zone.js will patch all possible macroTask and DomEvents
23 | * user can disable parts of macroTask/DomEvents patch by setting following flags
24 | * because those flags need to be set before `zone.js` being loaded, and webpack
25 | * will put import in the top of bundle, so user need to create a separate file
26 | * in this directory (for example: zone-flags.ts), and put the following flags
27 | * into that file, and then add the following code before importing zone.js.
28 | * import './zone-flags';
29 | *
30 | * The flags allowed in zone-flags.ts are listed here.
31 | *
32 | * The following flags will work for all browsers.
33 | *
34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
37 | *
38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
40 | *
41 | * (window as any).__Zone_enable_cross_context_check = true;
42 | *
43 | */
44 |
45 | /***************************************************************************************************
46 | * Zone JS is required by default for Angular itself.
47 | */
48 | import 'zone.js'; // Included with Angular CLI.
49 |
50 |
51 | /***************************************************************************************************
52 | * APPLICATION IMPORTS
53 | */
54 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
3 | html, body { height: 100%; }
4 | body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
5 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts",
10 | "src/polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitReturns": true,
10 | "noFallthroughCasesInSwitch": true,
11 | "sourceMap": true,
12 | "declaration": false,
13 | "downlevelIteration": true,
14 | "experimentalDecorators": true,
15 | "moduleResolution": "node",
16 | "importHelpers": true,
17 | "target": "es2017",
18 | "module": "es2020",
19 | "lib": [
20 | "es2018",
21 | "dom"
22 | ]
23 | },
24 | "angularCompilerOptions": {
25 | "enableI18nLegacyMessageIdFormat": false,
26 | "strictInjectionParameters": true,
27 | "strictInputAccessModifiers": true,
28 | "strictTemplates": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------