├── .gitignore ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── controls │ │ ├── currency │ │ │ ├── currency.component.html │ │ │ ├── currency.component.scss │ │ │ ├── currency.component.spec.ts │ │ │ └── currency.component.ts │ │ ├── number │ │ │ ├── number.component.html │ │ │ ├── number.component.scss │ │ │ ├── number.component.spec.ts │ │ │ └── number.component.ts │ │ ├── select │ │ │ ├── select.component.html │ │ │ ├── select.component.scss │ │ │ ├── select.component.spec.ts │ │ │ └── select.component.ts │ │ └── slider │ │ │ ├── slider.component.html │ │ │ ├── slider.component.scss │ │ │ ├── slider.component.spec.ts │ │ │ └── slider.component.ts │ ├── custom-material │ │ └── custom-material.module.ts │ ├── dynamic-complex-form │ │ ├── dynamic-complex-form.component.html │ │ ├── dynamic-complex-form.component.scss │ │ ├── dynamic-complex-form.component.spec.ts │ │ └── dynamic-complex-form.component.ts │ ├── dynamic-component-loading-scam │ │ ├── dynamic-component-loading-scam.component.html │ │ ├── dynamic-component-loading-scam.component.scss │ │ ├── dynamic-component-loading-scam.component.spec.ts │ │ └── dynamic-component-loading-scam.component.ts │ ├── dynamic-component-loading │ │ ├── dynamic-component-loading.component.html │ │ ├── dynamic-component-loading.component.scss │ │ ├── dynamic-component-loading.component.spec.ts │ │ └── dynamic-component-loading.component.ts │ ├── dynamic-form-control │ │ ├── dynamic-form-control.component.html │ │ ├── dynamic-form-control.component.scss │ │ ├── dynamic-form-control.component.spec.ts │ │ └── dynamic-form-control.component.ts │ ├── dynamic-simple-form │ │ ├── dynamic-simple-form.component.html │ │ ├── dynamic-simple-form.component.scss │ │ ├── dynamic-simple-form.component.spec.ts │ │ └── dynamic-simple-form.component.ts │ ├── home │ │ ├── home.component.html │ │ ├── home.component.scss │ │ ├── home.component.spec.ts │ │ └── home.component.ts │ ├── lazy │ │ ├── business-card │ │ │ ├── business-card.component.html │ │ │ ├── business-card.component.scss │ │ │ ├── business-card.component.spec.ts │ │ │ └── business-card.component.ts │ │ └── user-list │ │ │ ├── user-list.component.html │ │ │ ├── user-list.component.scss │ │ │ ├── user-list.component.spec.ts │ │ │ └── user-list.component.ts │ └── weather-forecast │ │ ├── assets │ │ └── weather-icons │ │ │ ├── 01d.png │ │ │ ├── 01n.png │ │ │ ├── 02d.png │ │ │ ├── 02n.png │ │ │ ├── 03d.png │ │ │ ├── 03n.png │ │ │ ├── 04d.png │ │ │ ├── 04n.png │ │ │ ├── 09d.png │ │ │ ├── 10d.png │ │ │ └── 10n.png │ │ ├── filters │ │ ├── filters.component.css │ │ ├── filters.component.html │ │ ├── filters.component.spec.ts │ │ └── filters.component.ts │ │ ├── navbar │ │ ├── navbar.component.css │ │ ├── navbar.component.html │ │ └── navbar.component.ts │ │ ├── services │ │ ├── dropdown.service.ts │ │ ├── location.service.ts │ │ └── weather.service.ts │ │ ├── weather-dashboard │ │ ├── weather-dashboard.component.css │ │ ├── weather-dashboard.component.html │ │ ├── weather-dashboard.component.spec.ts │ │ └── weather-dashboard.component.ts │ │ ├── weather-forecast-city │ │ ├── weather-forecast-city.component.css │ │ ├── weather-forecast-city.component.html │ │ ├── weather-forecast-city.component.spec.ts │ │ └── weather-forecast-city.component.ts │ │ ├── weather-forecast-details │ │ ├── weather-forecast-details.component.css │ │ ├── weather-forecast-details.component.html │ │ ├── weather-forecast-details.component.spec.ts │ │ └── weather-forecast-details.component.ts │ │ ├── weather-forecast-history │ │ ├── weather-forecast-history.component.css │ │ ├── weather-forecast-history.component.html │ │ └── weather-forecast-history.component.ts │ │ ├── weather-forecast.module.ts │ │ └── weather-forecast │ │ ├── weather-forecast.component.css │ │ ├── weather-forecast.component.html │ │ ├── weather-forecast.component.spec.ts │ │ └── weather-forecast.component.ts ├── assets │ ├── .gitkeep │ └── test.txt ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Demystify Angular Lazy Loading 2 | 3 | ## Agenda 4 | 5 | - Dyamic component loading 6 | - Dyamic component loading (ES 6 Import + SCAM) 7 | - Simple form dynamic loading (ES 6 Import + SCAM + ngComponentOutlet) 8 | - Complex form rendering (ES 6 Import + SCAM + ng-dynamic-component) 9 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "demystify-angular-lazy-loading": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | }, 12 | "@schematics/angular:application": { 13 | "strict": true 14 | } 15 | }, 16 | "root": "", 17 | "sourceRoot": "src", 18 | "prefix": "app", 19 | "architect": { 20 | "build": { 21 | "builder": "@angular-devkit/build-angular:browser", 22 | "options": { 23 | "outputPath": "dist/demystify-angular-lazy-loading", 24 | "index": "src/index.html", 25 | "main": "src/main.ts", 26 | "polyfills": "src/polyfills.ts", 27 | "tsConfig": "tsconfig.app.json", 28 | "aot": true, 29 | "assets": [ 30 | "src/favicon.ico", 31 | "src/assets", 32 | { 33 | "glob": "**/*", 34 | "input": "src/app/weather-forecast/assets", 35 | "output": "/assets/" 36 | } 37 | ], 38 | "styles": [ 39 | "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", 40 | "src/styles.scss" 41 | ], 42 | "scripts": [] 43 | }, 44 | "configurations": { 45 | "production": { 46 | "fileReplacements": [ 47 | { 48 | "replace": "src/environments/environment.ts", 49 | "with": "src/environments/environment.prod.ts" 50 | } 51 | ], 52 | "optimization": true, 53 | "outputHashing": "all", 54 | "sourceMap": false, 55 | "commonChunk": false, 56 | "namedChunks": false, 57 | "extractLicenses": true, 58 | "vendorChunk": false, 59 | "buildOptimizer": true, 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 | } 73 | } 74 | }, 75 | "serve": { 76 | "builder": "@angular-devkit/build-angular:dev-server", 77 | "options": { 78 | "browserTarget": "demystify-angular-lazy-loading:build" 79 | }, 80 | "configurations": { 81 | "production": { 82 | "browserTarget": "demystify-angular-lazy-loading:build:production" 83 | } 84 | } 85 | }, 86 | "extract-i18n": { 87 | "builder": "@angular-devkit/build-angular:extract-i18n", 88 | "options": { 89 | "browserTarget": "demystify-angular-lazy-loading:build" 90 | } 91 | }, 92 | "test": { 93 | "builder": "@angular-devkit/build-angular:karma", 94 | "options": { 95 | "main": "src/test.ts", 96 | "polyfills": "src/polyfills.ts", 97 | "tsConfig": "tsconfig.spec.json", 98 | "karmaConfig": "karma.conf.js", 99 | "assets": [ 100 | "src/favicon.ico", 101 | "src/assets" 102 | ], 103 | "styles": [ 104 | "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", 105 | "src/styles.scss" 106 | ], 107 | "scripts": [] 108 | } 109 | }, 110 | "lint": { 111 | "builder": "@angular-devkit/build-angular:tslint", 112 | "options": { 113 | "tsConfig": [ 114 | "tsconfig.app.json", 115 | "tsconfig.spec.json", 116 | "e2e/tsconfig.json" 117 | ], 118 | "exclude": [ 119 | "**/node_modules/**" 120 | ] 121 | } 122 | }, 123 | "e2e": { 124 | "builder": "@angular-devkit/build-angular:protractor", 125 | "options": { 126 | "protractorConfig": "e2e/protractor.conf.js", 127 | "devServerTarget": "demystify-angular-lazy-loading:serve" 128 | }, 129 | "configurations": { 130 | "production": { 131 | "devServerTarget": "demystify-angular-lazy-loading:serve:production" 132 | } 133 | } 134 | }, 135 | "deploy": { 136 | "builder": "angular-cli-ghpages:deploy", 137 | "options": {} 138 | } 139 | } 140 | } 141 | }, 142 | "defaultProject": "demystify-angular-lazy-loading", 143 | "cli": { 144 | "analytics": "8fd79776-d1be-4a50-a448-2d882e7b5430" 145 | } 146 | } -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | SELENIUM_PROMISE_MANAGER: false, 20 | baseUrl: 'http://localhost:4200/', 21 | framework: 'jasmine', 22 | jasmineNodeOpts: { 23 | showColors: true, 24 | defaultTimeoutInterval: 30000, 25 | print: function() {} 26 | }, 27 | onPrepare() { 28 | require('ts-node').register({ 29 | project: require('path').join(__dirname, './tsconfig.json') 30 | }); 31 | jasmine.getEnv().addReporter(new SpecReporter({ 32 | spec: { 33 | displayStacktrace: StacktraceOption.PRETTY 34 | } 35 | })); 36 | } 37 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', async () => { 12 | await page.navigateTo(); 13 | expect(await page.getTitleText()).toEqual('demystify-angular-lazy-loading app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | async navigateTo(): Promise { 5 | return browser.get(browser.baseUrl); 6 | } 7 | 8 | async getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.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/e2e", 6 | "module": "commonjs", 7 | "target": "es2018", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | jasmineHtmlReporter: { 19 | suppressAll: true // removes the duplicated traces 20 | }, 21 | coverageReporter: { 22 | dir: require('path').join(__dirname, './coverage/demystify-angular-lazy-loading'), 23 | subdir: '.', 24 | reporters: [ 25 | { type: 'html' }, 26 | { type: 'text-summary' } 27 | ] 28 | }, 29 | reporters: ['progress', 'kjhtml'], 30 | port: 9876, 31 | colors: true, 32 | logLevel: config.LOG_INFO, 33 | autoWatch: true, 34 | browsers: ['Chrome'], 35 | singleRun: false, 36 | restartOnFileChange: true 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demystify-angular-lazy-loading", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~11.0.1", 15 | "@angular/cdk": "^11.2.6", 16 | "@angular/common": "~11.0.1", 17 | "@angular/compiler": "~11.0.1", 18 | "@angular/core": "~11.0.1", 19 | "@angular/forms": "~11.0.1", 20 | "@angular/material": "^11.2.6", 21 | "@angular/platform-browser": "~11.0.1", 22 | "@angular/platform-browser-dynamic": "~11.0.1", 23 | "@angular/router": "~11.0.1", 24 | "ng-dynamic-component": "^8.0.0", 25 | "rxjs": "~6.6.0", 26 | "tslib": "^2.0.0", 27 | "zone.js": "~0.10.2" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "~0.1100.2", 31 | "@angular/cli": "~11.0.2", 32 | "@angular/compiler-cli": "~11.0.1", 33 | "@types/jasmine": "~3.6.0", 34 | "@types/node": "^12.11.1", 35 | "angular-cli-ghpages": "^1.0.0-rc.1", 36 | "codelyzer": "^6.0.0", 37 | "jasmine-core": "~3.6.0", 38 | "jasmine-spec-reporter": "~5.0.0", 39 | "karma": "~5.1.0", 40 | "karma-chrome-launcher": "~3.1.0", 41 | "karma-coverage": "~2.0.3", 42 | "karma-jasmine": "~4.0.0", 43 | "karma-jasmine-html-reporter": "^1.5.0", 44 | "protractor": "~7.0.0", 45 | "ts-node": "~8.3.0", 46 | "tslint": "~6.1.0", 47 | "typescript": "~4.0.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { HomeComponent } from './home/home.component'; 4 | import { DynamicComponentLoadingComponent } from './dynamic-component-loading/dynamic-component-loading.component'; 5 | import { DynamicComponentLoadingScamComponent } from './dynamic-component-loading-scam/dynamic-component-loading-scam.component'; 6 | import { DynamicComplexFormComponent } from './dynamic-complex-form/dynamic-complex-form.component'; 7 | import { DynamicSimpleFormComponent } from './dynamic-simple-form/dynamic-simple-form.component'; 8 | 9 | const routes: Routes = [ 10 | {path: 'home', component: HomeComponent}, 11 | {path: 'dynamic-component-loading', component: DynamicComponentLoadingComponent }, 12 | {path: 'dynamic-component-loading-scam', component: DynamicComponentLoadingScamComponent }, 13 | {path: 'dynamic-complex-form', component: DynamicComplexFormComponent }, 14 | {path: 'dynamic-simple-form', component: DynamicSimpleFormComponent }, 15 | {path: '', redirectTo: 'home', pathMatch: 'full'} 16 | ]; 17 | 18 | @NgModule({ 19 | imports: [RouterModule.forRoot(routes)], 20 | exports: [RouterModule] 21 | }) 22 | export class AppRoutingModule { } 23 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | Lazy Loading Recipes 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/app.component.scss -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async () => { 7 | await TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | }); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'demystify-angular-lazy-loading'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.componentInstance; 26 | expect(app.title).toEqual('demystify-angular-lazy-loading'); 27 | }); 28 | 29 | it('should render title', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.nativeElement; 33 | expect(compiled.querySelector('.content span').textContent).toContain('demystify-angular-lazy-loading app is running!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'demystify-angular-lazy-loading'; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 7 | import { CustomMaterialModule } from './custom-material/custom-material.module'; 8 | import { HttpClientJsonpModule, HttpClientModule } from '@angular/common/http'; 9 | import { DynamicIoModule } from 'ng-dynamic-component'; 10 | import { HomeComponent } from './home/home.component'; 11 | import { BusinessCardComponent } from './lazy/business-card/business-card.component'; 12 | import { UserListComponent } from './lazy/user-list/user-list.component'; 13 | import { DynamicComponentLoadingComponent } from './dynamic-component-loading/dynamic-component-loading.component'; 14 | import { DynamicComponentLoadingScamComponent } from './dynamic-component-loading-scam/dynamic-component-loading-scam.component'; 15 | import { DynamicSimpleFormComponent } from './dynamic-simple-form/dynamic-simple-form.component'; 16 | import { DynamicComplexFormComponent } from './dynamic-complex-form/dynamic-complex-form.component'; 17 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 18 | import { DynamicFormControlComponent } from './dynamic-form-control/dynamic-form-control.component'; 19 | 20 | @NgModule({ 21 | declarations: [ 22 | AppComponent, 23 | HomeComponent, 24 | BusinessCardComponent, 25 | UserListComponent, 26 | DynamicComponentLoadingComponent, 27 | DynamicComponentLoadingScamComponent, 28 | DynamicSimpleFormComponent, 29 | DynamicComplexFormComponent, 30 | DynamicFormControlComponent, 31 | ], 32 | imports: [ 33 | BrowserModule, 34 | AppRoutingModule, 35 | FormsModule, 36 | ReactiveFormsModule, 37 | BrowserAnimationsModule, 38 | CustomMaterialModule, 39 | HttpClientModule, 40 | HttpClientJsonpModule, 41 | DynamicIoModule 42 | ], 43 | providers: [], 44 | bootstrap: [AppComponent] 45 | }) 46 | export class AppModule { } 47 | -------------------------------------------------------------------------------- /src/app/controls/currency/currency.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ placeholder }} 3 | 4 | 5 | .00 6 | -------------------------------------------------------------------------------- /src/app/controls/currency/currency.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/controls/currency/currency.component.scss -------------------------------------------------------------------------------- /src/app/controls/currency/currency.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CurrencyComponent } from './currency.component'; 4 | 5 | describe('CurrencyComponent', () => { 6 | let component: CurrencyComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CurrencyComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CurrencyComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/controls/currency/currency.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { 3 | ChangeDetectionStrategy, Component, OnInit, Self, NgModule, Input, Optional 4 | } from '@angular/core'; 5 | import { ControlValueAccessor, FormControl, FormGroup, FormsModule, NgControl, ReactiveFormsModule } from '@angular/forms'; 6 | import { MatFormFieldModule } from '@angular/material/form-field'; 7 | import { MatInputModule } from '@angular/material/input'; 8 | import { Subscription } from 'rxjs'; 9 | 10 | @Component({ 11 | selector: 'app-currency', 12 | templateUrl: './currency.component.html', 13 | styleUrls: ['./currency.component.scss'], 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | }) 16 | export class CurrencyComponent implements OnInit, ControlValueAccessor { 17 | 18 | // @Input() valueAccessor: ControlValueAccessor | null; 19 | @Input() group: FormGroup = new FormGroup({}); 20 | @Input() formControlName!: string; 21 | @Input() placeholder = 'Enter a value'; 22 | 23 | control!: FormControl; 24 | 25 | subscriptions: Subscription[] = []; 26 | options = []; 27 | 28 | constructor( 29 | @Self() @Optional() public ngControl: NgControl, 30 | ) { 31 | if (this.ngControl) { 32 | this.ngControl.valueAccessor = this; 33 | } 34 | // if (this.valueAccessor) { 35 | // this.ngControl.valueAccessor = this.valueAccessor; 36 | // } 37 | } 38 | 39 | //ControlValueAccessor interface 40 | writeValue(_: any) { } 41 | 42 | registerOnChange(_: any) { } 43 | 44 | registerOnTouched(_: any) { } 45 | 46 | setDisabledState?(_: boolean) { } 47 | 48 | ngOnInit(): void { 49 | this.control = this.formControlName ? this.group.get(this.formControlName) as FormControl: new FormControl(); 50 | } 51 | } 52 | 53 | 54 | @NgModule({ 55 | imports: [ 56 | CommonModule, 57 | MatFormFieldModule, 58 | MatInputModule, 59 | ReactiveFormsModule, 60 | FormsModule, 61 | ], 62 | declarations: [CurrencyComponent], 63 | exports: [CurrencyComponent] 64 | }) 65 | export class CurrencyModule { 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/app/controls/number/number.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ placeholder }} 3 | 4 | -------------------------------------------------------------------------------- /src/app/controls/number/number.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/controls/number/number.component.scss -------------------------------------------------------------------------------- /src/app/controls/number/number.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NumberComponent } from './number.component'; 4 | 5 | describe('NumberComponent', () => { 6 | let component: NumberComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ NumberComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(NumberComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/controls/number/number.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { 3 | ChangeDetectionStrategy, Component, OnInit, Self, NgModule, Input, Optional 4 | } from '@angular/core'; 5 | import { ControlValueAccessor, FormControl, FormGroup, FormsModule, NgControl, ReactiveFormsModule } from '@angular/forms'; 6 | import { MatFormFieldModule } from '@angular/material/form-field'; 7 | import { MatInputModule } from '@angular/material/input'; 8 | import { Subscription } from 'rxjs'; 9 | 10 | @Component({ 11 | selector: 'app-number', 12 | templateUrl: './number.component.html', 13 | styleUrls: ['./number.component.scss'], 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | }) 16 | export class NumberComponent implements OnInit, ControlValueAccessor { 17 | 18 | // @Input() valueAccessor: ControlValueAccessor | null; 19 | @Input() group: FormGroup = new FormGroup({}); 20 | @Input() formControlName!: string; 21 | @Input() placeholder = 'Enter a value'; 22 | 23 | control!: FormControl; 24 | 25 | subscriptions: Subscription[] = []; 26 | 27 | constructor( 28 | @Self() @Optional() public ngControl: NgControl, 29 | ) { 30 | if (this.ngControl) { 31 | this.ngControl.valueAccessor = this; 32 | } 33 | } 34 | 35 | //ControlValueAccessor interface 36 | writeValue(_: any) { } 37 | 38 | registerOnChange(_: any) { } 39 | 40 | registerOnTouched(_: any) { } 41 | 42 | setDisabledState?(_: boolean) { } 43 | 44 | ngOnInit(): void { 45 | this.control = this.formControlName ?this.group.get(this.formControlName) as FormControl: new FormControl();; 46 | } 47 | } 48 | 49 | @NgModule({ 50 | imports: [ 51 | CommonModule, 52 | MatFormFieldModule, 53 | MatInputModule, 54 | FormsModule, ReactiveFormsModule 55 | ], 56 | declarations: [NumberComponent], 57 | exports: [NumberComponent] 58 | }) 59 | export class NumberModule { 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/app/controls/select/select.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ placeholder }} 3 | 4 | 5 | {{option}} 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/controls/select/select.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/controls/select/select.component.scss -------------------------------------------------------------------------------- /src/app/controls/select/select.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SelectComponent } from './select.component'; 4 | 5 | describe('SelectComponent', () => { 6 | let component: SelectComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ SelectComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SelectComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/controls/select/select.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { 3 | ChangeDetectionStrategy, Component, OnInit, Self, NgModule, Input, Optional 4 | } from '@angular/core'; 5 | import { ControlValueAccessor, FormControl, FormGroup, FormsModule, NgControl, ReactiveFormsModule } from '@angular/forms'; 6 | import { MatFormFieldModule } from '@angular/material/form-field'; 7 | import { MatSelectModule } from '@angular/material/select'; 8 | import { Subscription } from 'rxjs'; 9 | 10 | @Component({ 11 | selector: 'app-select', 12 | templateUrl: './select.component.html', 13 | styleUrls: ['./select.component.scss'], 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | }) 16 | export class SelectComponent implements OnInit, ControlValueAccessor { 17 | 18 | // @Input() valueAccessor: ControlValueAccessor | null; 19 | @Input() group: FormGroup = new FormGroup({}); 20 | @Input() formControlName!: string; 21 | @Input() placeholder = 'Enter a value'; 22 | @Input() options = []; 23 | 24 | control!: FormControl; 25 | 26 | subscriptions: Subscription[] = []; 27 | 28 | constructor( 29 | @Self() @Optional() public ngControl: NgControl, 30 | ) { 31 | if (this.ngControl) { 32 | this.ngControl.valueAccessor = this; 33 | } 34 | } 35 | 36 | //ControlValueAccessor interface 37 | writeValue(_: any) { } 38 | 39 | registerOnChange(_: any) { } 40 | 41 | registerOnTouched(_: any) { } 42 | 43 | setDisabledState?(_: boolean) { } 44 | 45 | ngOnInit(): void { 46 | this.control = this.formControlName ?this.group.get(this.formControlName) as FormControl: new FormControl();; 47 | } 48 | } 49 | 50 | @NgModule({ 51 | imports: [CommonModule,MatFormFieldModule, MatSelectModule, FormsModule, ReactiveFormsModule], 52 | declarations: [SelectComponent], 53 | exports: [SelectComponent] 54 | }) 55 | export class SelectModule { 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/app/controls/slider/slider.component.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/controls/slider/slider.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/controls/slider/slider.component.scss -------------------------------------------------------------------------------- /src/app/controls/slider/slider.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SliderComponent } from './slider.component'; 4 | 5 | describe('SliderComponent', () => { 6 | let component: SliderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ SliderComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SliderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/controls/slider/slider.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { 3 | ChangeDetectionStrategy, Component, OnInit, Self, NgModule, Input, Optional 4 | } from '@angular/core'; 5 | import { ControlValueAccessor, FormControl, FormGroup, FormsModule, NgControl, ReactiveFormsModule } from '@angular/forms'; 6 | import { MatFormFieldModule } from '@angular/material/form-field'; 7 | import { MatSelectModule } from '@angular/material/select'; 8 | import { Subscription } from 'rxjs'; 9 | import {MatSliderModule} from '@angular/material/slider'; 10 | 11 | @Component({ 12 | selector: 'app-slider', 13 | templateUrl: './slider.component.html', 14 | styleUrls: ['./slider.component.scss'] 15 | }) 16 | export class SliderComponent implements OnInit { 17 | 18 | @Input() group: FormGroup = new FormGroup({}); 19 | @Input() formControlName!: string; 20 | @Input() placeholder = 'Enter a value'; 21 | @Input() options = []; 22 | 23 | control!: FormControl; 24 | 25 | subscriptions: Subscription[] = []; 26 | 27 | constructor( 28 | @Self() @Optional() public ngControl: NgControl, 29 | ) { 30 | if (this.ngControl) { 31 | this.ngControl.valueAccessor = this; 32 | } 33 | } 34 | 35 | //ControlValueAccessor interface 36 | writeValue(_: any) { } 37 | 38 | registerOnChange(_: any) { } 39 | 40 | registerOnTouched(_: any) { } 41 | 42 | setDisabledState?(_: boolean) { } 43 | 44 | ngOnInit(): void { 45 | this.control = this.formControlName ? this.group.get(this.formControlName) as FormControl: new FormControl();; 46 | } 47 | } 48 | 49 | @NgModule({ 50 | imports: [CommonModule, MatFormFieldModule, MatSliderModule, FormsModule, ReactiveFormsModule], 51 | declarations: [SliderComponent], 52 | exports: [SliderComponent] 53 | }) 54 | export class SliderModule { 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/app/custom-material/custom-material.module.ts: -------------------------------------------------------------------------------- 1 | 2 | import {NgModule} from '@angular/core'; 3 | import {A11yModule} from '@angular/cdk/a11y'; 4 | import {MatAutocompleteModule} from '@angular/material/autocomplete'; 5 | import {MatButtonModule} from '@angular/material/button'; 6 | import {MatButtonToggleModule} from '@angular/material/button-toggle'; 7 | import {MatCardModule} from '@angular/material/card'; 8 | import {MatCheckboxModule} from '@angular/material/checkbox'; 9 | import {MatDatepickerModule} from '@angular/material/datepicker'; 10 | import {MatDialogModule} from '@angular/material/dialog'; 11 | import {MatDividerModule} from '@angular/material/divider'; 12 | import {MatExpansionModule} from '@angular/material/expansion'; 13 | import {MatGridListModule} from '@angular/material/grid-list'; 14 | import {MatIconModule} from '@angular/material/icon'; 15 | import {MatInputModule} from '@angular/material/input'; 16 | import {MatListModule} from '@angular/material/list'; 17 | import {MatMenuModule} from '@angular/material/menu'; 18 | import {MatNativeDateModule, MatRippleModule} from '@angular/material/core'; 19 | import {MatProgressBarModule} from '@angular/material/progress-bar'; 20 | import {MatRadioModule} from '@angular/material/radio'; 21 | import {MatSelectModule} from '@angular/material/select'; 22 | import {MatSnackBarModule} from '@angular/material/snack-bar'; 23 | import {MatSortModule} from '@angular/material/sort'; 24 | import {MatTooltipModule} from '@angular/material/tooltip'; 25 | import {MatToolbarModule} from '@angular/material/toolbar'; 26 | import {MatFormFieldModule} from '@angular/material/form-field'; 27 | 28 | @NgModule({ 29 | exports: [ 30 | A11yModule, 31 | MatAutocompleteModule, 32 | MatButtonModule, 33 | MatButtonToggleModule, 34 | MatCardModule, 35 | MatCheckboxModule, 36 | MatDatepickerModule, 37 | MatDialogModule, 38 | MatDividerModule, 39 | MatExpansionModule, 40 | MatGridListModule, 41 | MatIconModule, 42 | MatInputModule, 43 | MatListModule, 44 | MatMenuModule, 45 | MatNativeDateModule, 46 | MatProgressBarModule, 47 | MatRadioModule, 48 | MatRippleModule, 49 | MatSelectModule, 50 | MatSnackBarModule, 51 | MatSortModule, 52 | MatTooltipModule, 53 | MatToolbarModule, 54 | MatFormFieldModule 55 | ] 56 | }) 57 | export class CustomMaterialModule { } 58 | -------------------------------------------------------------------------------- /src/app/dynamic-complex-form/dynamic-complex-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Profile Form

3 |
4 | 7 |
8 | 11 |
12 | 13 | {{ feedbackForm.value | json }} -------------------------------------------------------------------------------- /src/app/dynamic-complex-form/dynamic-complex-form.component.scss: -------------------------------------------------------------------------------- 1 | form { 2 | padding: 15px; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/dynamic-complex-form/dynamic-complex-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DynamicComplexFormComponent } from './dynamic-complex-form.component'; 4 | 5 | describe('DynamicComplexFormComponent', () => { 6 | let component: DynamicComplexFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ DynamicComplexFormComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DynamicComplexFormComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/dynamic-complex-form/dynamic-complex-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup } from '@angular/forms'; 3 | 4 | interface Controls{ 5 | type: string, 6 | inputs: { 7 | value: any; 8 | formControlName: any; 9 | group: FormGroup, 10 | options?: any; 11 | }, 12 | } 13 | 14 | @Component({ 15 | selector: 'app-dynamic-complex-form', 16 | templateUrl: './dynamic-complex-form.component.html', 17 | styleUrls: ['./dynamic-complex-form.component.scss'] 18 | }) 19 | export class DynamicComplexFormComponent implements OnInit { 20 | 21 | feedbackForm = new FormGroup({}); 22 | 23 | componentsMap: {[key: string]: Promise|null} = { 24 | currency: import('./../controls/currency/currency.component').then(m => m.CurrencyComponent), 25 | number: import('./../controls/number/number.component').then(m => m.NumberComponent), 26 | select: import('./../controls/select/select.component').then(m => m.SelectComponent), 27 | slider: import('./../controls/slider/slider.component').then(m => m.SliderComponent), 28 | }; 29 | 30 | controls: Controls[] = [ 31 | { type: 'currency', inputs: { value: 1000, formControlName: 'price', group: this.feedbackForm }}, 32 | { type: 'number', inputs: {formControlName: 'age', value: 100, group: this.feedbackForm} }, 33 | { type: 'select', inputs: {options: ['Mumbai', 'Pune', 'Delhi'], formControlName: 'city', value: 'Mumbai', group: this.feedbackForm} }, 34 | { type: 'slider', inputs: {formControlName: 'rating', value: 1, group: this.feedbackForm} }, 35 | ]; 36 | 37 | constructor() { } 38 | 39 | createForm(controls: Controls[] = []) { 40 | controls.forEach(control => { 41 | this.feedbackForm.addControl( 42 | control.inputs.formControlName, 43 | new FormControl(control.inputs.value || '') 44 | ); 45 | console.log(this.feedbackForm.get(control.inputs.formControlName)); 46 | }); 47 | } 48 | 49 | submit() { 50 | console.log('feedbackForm value', this.feedbackForm.value); 51 | } 52 | 53 | async ngOnInit() { 54 | this.createForm(this.controls); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/app/dynamic-component-loading-scam/dynamic-component-loading-scam.component.html: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/app/dynamic-component-loading-scam/dynamic-component-loading-scam.component.scss: -------------------------------------------------------------------------------- 1 | button { 2 | margin: 5px; 3 | } -------------------------------------------------------------------------------- /src/app/dynamic-component-loading-scam/dynamic-component-loading-scam.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DynamicComponentLoadingScamComponent } from './dynamic-component-loading-scam.component'; 4 | 5 | describe('DynamicComponentLoadingScamComponent', () => { 6 | let component: DynamicComponentLoadingScamComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ DynamicComponentLoadingScamComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DynamicComponentLoadingScamComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/dynamic-component-loading-scam/dynamic-component-loading-scam.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ComponentFactoryResolver, OnInit, Type, ViewChild, ViewContainerRef } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-dynamic-component-loading-scam', 5 | templateUrl: './dynamic-component-loading-scam.component.html', 6 | styleUrls: ['./dynamic-component-loading-scam.component.scss'] 7 | }) 8 | export class DynamicComponentLoadingScamComponent implements OnInit { 9 | 10 | @ViewChild('container', { read: ViewContainerRef }) container: any; 11 | 12 | constructor( 13 | private factoryResolver: ComponentFactoryResolver 14 | ) { } 15 | 16 | async loadWeatherWidget() { 17 | this.clear(); 18 | const weatherComponent = await import('../weather-forecast/weather-dashboard/weather-dashboard.component').then(i => i.WeatherDashboardComponent); 19 | const weatherForecastWidget = this.factoryResolver.resolveComponentFactory(weatherComponent); 20 | const componentRef = this.container.createComponent(weatherForecastWidget); 21 | componentRef.instance.headingStart = 'Just for fun'; 22 | } 23 | 24 | clear() { 25 | this.container.clear(); 26 | } 27 | 28 | ngOnInit(): void { 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/app/dynamic-component-loading/dynamic-component-loading.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 | 9 |
10 | 13 |
14 |
15 | 16 |
-------------------------------------------------------------------------------- /src/app/dynamic-component-loading/dynamic-component-loading.component.scss: -------------------------------------------------------------------------------- 1 | .dynamic-component-loading-wrapper { 2 | display: flex; 3 | button { 4 | margin: 5px; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/app/dynamic-component-loading/dynamic-component-loading.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DynamicComponentLoadingComponent } from './dynamic-component-loading.component'; 4 | 5 | describe('DynamicComponentLoadingComponent', () => { 6 | let component: DynamicComponentLoadingComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ DynamicComponentLoadingComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DynamicComponentLoadingComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/dynamic-component-loading/dynamic-component-loading.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ComponentFactoryResolver, OnInit, Type, ViewChild, ViewContainerRef } from '@angular/core'; 2 | import { BusinessCardComponent } from '../lazy/business-card/business-card.component'; 3 | import { UserListComponent } from '../lazy/user-list/user-list.component'; 4 | 5 | const users = ['Boots', 'Clogs', 'Loafers', 'Moccasins', 'Sneakers']; 6 | 7 | @Component({ 8 | selector: 'app-dynamic-component-loading', 9 | templateUrl: './dynamic-component-loading.component.html', 10 | styleUrls: ['./dynamic-component-loading.component.scss'] 11 | }) 12 | export class DynamicComponentLoadingComponent implements OnInit { 13 | 14 | @ViewChild('dynamicComponent', { read: ViewContainerRef }) dynamicComponent: any; 15 | 16 | profile = { 17 | title: 'Shiba Inu', 18 | subtitle: 'Dog Breed', 19 | description: `The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. 20 | A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally 21 | bred for hunting.`, 22 | image: 'https://material.angular.io/assets/img/examples/shiba2.jpg' 23 | } 24 | 25 | constructor( 26 | private factoryResolver: ComponentFactoryResolver 27 | ) { } 28 | 29 | showBusinessCard() { 30 | this.clear(); 31 | const compFactory = this.factoryResolver.resolveComponentFactory(BusinessCardComponent); 32 | const compRef = this.dynamicComponent.createComponent(compFactory); 33 | compRef.instance.profile = this.profile; 34 | } 35 | 36 | showUserList() { 37 | this.clear(); 38 | const compFactory = this.factoryResolver.resolveComponentFactory(UserListComponent); 39 | const compRef = this.dynamicComponent.createComponent(compFactory); 40 | compRef.instance.users = users; 41 | } 42 | 43 | clear() { 44 | this.dynamicComponent.clear(); 45 | } 46 | 47 | ngOnInit(): void { 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/app/dynamic-form-control/dynamic-form-control.component.html: -------------------------------------------------------------------------------- 1 |

dynamic-form-control works!

2 | -------------------------------------------------------------------------------- /src/app/dynamic-form-control/dynamic-form-control.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/dynamic-form-control/dynamic-form-control.component.scss -------------------------------------------------------------------------------- /src/app/dynamic-form-control/dynamic-form-control.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DynamicFormControlComponent } from './dynamic-form-control.component'; 4 | 5 | describe('DynamicFormControlComponent', () => { 6 | let component: DynamicFormControlComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ DynamicFormControlComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DynamicFormControlComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/dynamic-form-control/dynamic-form-control.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-dynamic-form-control', 5 | templateUrl: './dynamic-form-control.component.html', 6 | styleUrls: ['./dynamic-form-control.component.scss'] 7 | }) 8 | export class DynamicFormControlComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/dynamic-simple-form/dynamic-simple-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Profile Form

3 |
4 | 6 | 7 |
8 | 11 |
12 | -------------------------------------------------------------------------------- /src/app/dynamic-simple-form/dynamic-simple-form.component.scss: -------------------------------------------------------------------------------- 1 | form { 2 | padding: 15px; 3 | } -------------------------------------------------------------------------------- /src/app/dynamic-simple-form/dynamic-simple-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DynamicSimpleFormComponent } from './dynamic-simple-form.component'; 4 | 5 | describe('DynamicSimpleFormComponent', () => { 6 | let component: DynamicSimpleFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ DynamicSimpleFormComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DynamicSimpleFormComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/dynamic-simple-form/dynamic-simple-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-dynamic-simple-form', 5 | templateUrl: './dynamic-simple-form.component.html', 6 | styleUrls: ['./dynamic-simple-form.component.scss'] 7 | }) 8 | export class DynamicSimpleFormComponent implements OnInit { 9 | 10 | controls: {component?: Promise|null}[] = [ 11 | { component: import('./../controls/currency/currency.component').then(c => c.CurrencyComponent) }, 12 | { component: import('./../controls/number/number.component').then(c => c.NumberComponent) }, 13 | { component: import('./../controls/select/select.component').then(c => c.SelectComponent) }, 14 | { component: import('./../controls/slider/slider.component').then(c => c.SliderComponent) }, 15 | ] 16 | 17 | constructor() { } 18 | 19 | submit() { 20 | console.log('Called'); 21 | } 22 | 23 | ngOnInit(): void { 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Dynamic Component Loading 8 | check_circle 9 | 10 | 11 | 12 | 13 |

14 | Load component using Factory Resolver and inject the imported component directly to the DOM. 15 |

16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | import + SCAM 25 | check_circle 26 | 27 | 28 | 29 | 30 |

31 | Using SCAM (Single Component Angular Module) pattern, we will import the component as an when needed. 32 | And will lazily load the same onto Page. 33 |

34 |
35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | Config based simple form 43 | warning 44 | 45 | 46 | 47 | 48 |

49 | We're extended the last implementation logic. Now we used "SCAM + import + ngComponentOutlet" to add component on DOM. 50 |

51 |
52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | Config based complex form 60 | check_circle 61 | 62 | 63 | 64 | 65 |

66 | Now complete form will be rendered based on configuration provided from the server. 67 | "ng-dynamic-component + import + SCAM" 68 |

69 |
70 |
71 |
72 |
73 | -------------------------------------------------------------------------------- /src/app/home/home.component.scss: -------------------------------------------------------------------------------- 1 | mat-card { 2 | width: 100%; 3 | margin: 5px; 4 | } 5 | .green { 6 | color: green; 7 | } 8 | .yellow { 9 | color: yellow; 10 | } -------------------------------------------------------------------------------- /src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.scss'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/lazy/business-card/business-card.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | {{profile.title}} 5 | {{profile.subtitle}} 6 |
7 | Photo of a Shiba Inu 8 | 9 |

10 | {{profile.description}} 11 |

12 |
13 | 14 | 15 | 16 | 17 |
-------------------------------------------------------------------------------- /src/app/lazy/business-card/business-card.component.scss: -------------------------------------------------------------------------------- 1 | .mat-card { 2 | width: 400px; 3 | } -------------------------------------------------------------------------------- /src/app/lazy/business-card/business-card.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { BusinessCardComponent } from './business-card.component'; 4 | 5 | describe('BusinessCardComponent', () => { 6 | let component: BusinessCardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ BusinessCardComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(BusinessCardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/lazy/business-card/business-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-business-card', 5 | templateUrl: './business-card.component.html', 6 | styleUrls: ['./business-card.component.scss'] 7 | }) 8 | export class BusinessCardComponent implements OnInit { 9 | 10 | @Input() profile!: any; 11 | 12 | constructor() { } 13 | 14 | ngOnInit(): void { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/lazy/user-list/user-list.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{user}} 4 | 5 | -------------------------------------------------------------------------------- /src/app/lazy/user-list/user-list.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/lazy/user-list/user-list.component.scss -------------------------------------------------------------------------------- /src/app/lazy/user-list/user-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserListComponent } from './user-list.component'; 4 | 5 | describe('UserListComponent', () => { 6 | let component: UserListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ UserListComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(UserListComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/lazy/user-list/user-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-user-list', 5 | templateUrl: './user-list.component.html', 6 | styleUrls: ['./user-list.component.scss'] 7 | }) 8 | export class UserListComponent implements OnInit { 9 | 10 | @Input() users = []; 11 | 12 | constructor() { } 13 | 14 | ngOnInit(): void { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/weather-forecast/assets/weather-icons/01d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/assets/weather-icons/01d.png -------------------------------------------------------------------------------- /src/app/weather-forecast/assets/weather-icons/01n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/assets/weather-icons/01n.png -------------------------------------------------------------------------------- /src/app/weather-forecast/assets/weather-icons/02d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/assets/weather-icons/02d.png -------------------------------------------------------------------------------- /src/app/weather-forecast/assets/weather-icons/02n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/assets/weather-icons/02n.png -------------------------------------------------------------------------------- /src/app/weather-forecast/assets/weather-icons/03d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/assets/weather-icons/03d.png -------------------------------------------------------------------------------- /src/app/weather-forecast/assets/weather-icons/03n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/assets/weather-icons/03n.png -------------------------------------------------------------------------------- /src/app/weather-forecast/assets/weather-icons/04d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/assets/weather-icons/04d.png -------------------------------------------------------------------------------- /src/app/weather-forecast/assets/weather-icons/04n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/assets/weather-icons/04n.png -------------------------------------------------------------------------------- /src/app/weather-forecast/assets/weather-icons/09d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/assets/weather-icons/09d.png -------------------------------------------------------------------------------- /src/app/weather-forecast/assets/weather-icons/10d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/assets/weather-icons/10d.png -------------------------------------------------------------------------------- /src/app/weather-forecast/assets/weather-icons/10n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/assets/weather-icons/10n.png -------------------------------------------------------------------------------- /src/app/weather-forecast/filters/filters.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/filters/filters.component.css -------------------------------------------------------------------------------- /src/app/weather-forecast/filters/filters.component.html: -------------------------------------------------------------------------------- 1 |

Weather Filters

2 |
3 | 4 | 5 | 6 | 7 | {{country.name}}-{{country.code}} 8 | 9 | 10 | 11 | 12 | 13 | 14 | {{state.region}} 15 | 16 | 17 | 18 | 19 | 20 | 21 | {{city.city}} 22 | 23 | 24 | 25 | 26 |
27 |
28 | 29 | 30 |
-------------------------------------------------------------------------------- /src/app/weather-forecast/filters/filters.component.spec.ts: -------------------------------------------------------------------------------- 1 | // import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | // import { CUSTOM_ELEMENTS_SCHEMA, TemplateRef, ViewChild, Component, ViewContainerRef, Directive } from '@angular/core'; 3 | // import { MatDialogRef } from '@angular/material'; 4 | // import { BrowserModule } from '@angular/platform-browser'; 5 | // import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 6 | // import { Observable } from 'rxjs/Observable'; 7 | 8 | // import { LocationService } from '../services/location.service'; 9 | // import { WeatherService } from '../services/weather.service'; 10 | // import { WeatherForecastComponent } from '../weather-forecast/weather-forecast.component'; 11 | // import { CustomMaterialModule } from '../custom-material/custom-material.module'; 12 | // import { AppModule } from '../app.module'; 13 | // import { countries, states, cities, ipData } from '../../test/mock-data' 14 | 15 | // import { FiltersComponent } from './filters.component'; 16 | // import { DropdownService } from '../services/dropdown.service'; 17 | 18 | // @Directive({selector: 'dir-with-view-container'}) 19 | // class DirectiveWithViewContainer { 20 | // constructor(public viewContainerRef: ViewContainerRef) { } 21 | // } 22 | 23 | // @Component({ 24 | // selector: 'arbitrary-component', 25 | // template: ``, 26 | // }) 27 | // class ComponentWithChildViewContainer { 28 | // @ViewChild(DirectiveWithViewContainer) childWithViewContainer: DirectiveWithViewContainer; 29 | 30 | // get childViewContainer() { 31 | // return this.childWithViewContainer.viewContainerRef; 32 | // } 33 | // } 34 | 35 | // @Component({ 36 | // selector: 'arbitrary-component-with-template-ref', 37 | // template: ` 38 | // Cheese {{localValue}} {{data?.value}}{{setDialogRef(dialogRef)}}`, 39 | // }) 40 | // class ComponentWithTemplateRef { 41 | // localValue: string; 42 | // dialogRef: MatDialogRef; 43 | 44 | // @ViewChild(TemplateRef) templateRef: TemplateRef; 45 | 46 | // setDialogRef(dialogRef: MatDialogRef): string { 47 | // this.dialogRef = dialogRef; 48 | // return ''; 49 | // } 50 | // } 51 | 52 | // describe('DashboardComponent', () => { 53 | // let component: FiltersComponent, 54 | // fixture: ComponentFixture, 55 | // locationService: LocationService, 56 | // dialogRef: MatDialogRef, 57 | // dropdownService: DropdownService; 58 | 59 | // beforeEach(() => { 60 | 61 | 62 | // let stubLocationService = { 63 | // getIPData: () => Observable.of(ipData), 64 | // ipData: ipData, 65 | // ipData$: new BehaviorSubject(ipData) 66 | // }, stubDropdownService = { 67 | // getCities: () => Observable.of(cities), 68 | // getCountries: () => Observable.of(countries), 69 | // getRegions: () => Observable.of(states) 70 | // }; 71 | 72 | // TestBed.configureTestingModule({ 73 | // imports: [AppModule], 74 | // schemas: [CUSTOM_ELEMENTS_SCHEMA], 75 | // providers: [ 76 | // { provide: LocationService, useValue: stubLocationService }, 77 | // { provide: DropdownService, useValue: stubDropdownService }, 78 | // ] 79 | // }); 80 | 81 | // fixture = TestBed.createComponent(FiltersComponent); 82 | // // fixture.debugElement 83 | // let matDialogRef = fixture.debugElement.injector.get(MatDialogRef); 84 | // matDialogRef 85 | // component = fixture.componentInstance; 86 | // }); 87 | 88 | 89 | // describe('Default values', () => { 90 | // it('location should have loaded with city, state and country', () => { 91 | // //after component loaded 92 | // fixture.detectChanges(); 93 | // expect(component.locationData).toBeDefined(); 94 | // }); 95 | 96 | // it('should open first accordion', () => { 97 | // fixture.detectChanges(); 98 | // expect(component.locationData).toBeDefined(); 99 | // }); 100 | // }); 101 | // }); 102 | -------------------------------------------------------------------------------- /src/app/weather-forecast/filters/filters.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { MatDialogRef } from '@angular/material/dialog'; 3 | import { Subscription } from 'rxjs'; 4 | 5 | import { LocationService } from '../services/location.service'; 6 | import { DropdownService } from '../services/dropdown.service'; 7 | 8 | @Component({ 9 | selector: 'wc-filters', 10 | templateUrl: './filters.component.html', 11 | styleUrls: ['./filters.component.css'] 12 | }) 13 | export class FiltersComponent implements OnInit { 14 | 15 | cities: any; 16 | countries: any; 17 | states: any; 18 | locationData: any; 19 | 20 | constructor( 21 | private locationService: LocationService, 22 | private dialogRef: MatDialogRef, 23 | private dropdownService: DropdownService 24 | ) { } 25 | 26 | loadDropdownData(ipData: any) { 27 | this.getCountries(); 28 | this.getRegions(ipData.country_code); 29 | this.getCities(ipData.country_code, ipData.region); 30 | } 31 | 32 | ngOnInit() { 33 | this.locationService.ipDataGetter().subscribe(ipData => { 34 | this.locationData = this.locationService.ipData; 35 | this.loadDropdownData(this.locationData); 36 | }); 37 | } 38 | 39 | getCities(countryCode: string, regionName: string) { 40 | this.dropdownService.getCities(countryCode, regionName).subscribe( 41 | cities => { 42 | this.cities = cities; 43 | } 44 | ); 45 | } 46 | 47 | getCountries() { 48 | this.dropdownService.getCountries().subscribe( 49 | countries => this.countries = countries 50 | ); 51 | } 52 | 53 | getRegions(regionName: string) { 54 | this.dropdownService.getRegions(regionName).subscribe( 55 | states => { 56 | this.states = states; 57 | } 58 | ); 59 | } 60 | 61 | close(data?: any) { 62 | this.dialogRef.close(data); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/app/weather-forecast/navbar/navbar.component.css: -------------------------------------------------------------------------------- 1 | .toolbar-icon { 2 | padding: 0 14px; 3 | } 4 | 5 | .toolbar-spacer { 6 | flex: 1 1 auto; 7 | } 8 | 9 | .fitlers{ 10 | color: black 11 | } 12 | 13 | .navbar{ 14 | width: 600px; 15 | margin: auto; 16 | } -------------------------------------------------------------------------------- /src/app/weather-forecast/navbar/navbar.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Weather Cast 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/app/weather-forecast/navbar/navbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { MatDialog } from '@angular/material'; 3 | 4 | import { FiltersComponent } from '../filters/filters.component'; 5 | import { LocationService } from '../services/location.service'; 6 | 7 | @Component({ 8 | selector: 'wc-navbar', 9 | templateUrl: './navbar.component.html', 10 | styleUrls: ['./navbar.component.css'] 11 | }) 12 | export class NavbarComponent implements OnInit { 13 | 14 | constructor(private matDialog: MatDialog, private locationService: LocationService) { } 15 | 16 | ngOnInit() { 17 | } 18 | 19 | openFilters() { 20 | this.matDialog.open(FiltersComponent).afterClosed().subscribe( 21 | changedIpData => { 22 | if (changedIpData) { 23 | this.locationService.ipDataSetter(changedIpData); 24 | } 25 | } 26 | ); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/app/weather-forecast/services/dropdown.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | 4 | import { map } from 'rxjs/operators' 5 | 6 | const COUNTRY_API_ENDPOINT = 'https://battuta.medunes.net/api/country/all/'; 7 | const REGION_API_ENDPOINT = 'https://battuta.medunes.net/api/region/'; 8 | const CITY_API_ENDPOINT = 'https://battuta.medunes.net/api/city/'; 9 | const BATTUTA_API_KEY = '1b5ebf4ff05bb62385c55f0a141daba9'; 10 | 11 | const COUNTRY_API_URL = `${COUNTRY_API_ENDPOINT}?key=${BATTUTA_API_KEY}`; 12 | 13 | @Injectable({ 14 | providedIn: 'root' 15 | }) 16 | export class DropdownService { 17 | 18 | constructor(private httpClient: HttpClient) { } 19 | 20 | getCities(countryCode: string, regionName: string) { 21 | return this.httpClient.jsonp(`${CITY_API_ENDPOINT}${countryCode}/search/?region=${regionName}&key=${BATTUTA_API_KEY}`, 'callback') 22 | .pipe( 23 | map((countries: any) => { 24 | return countries.map((country: any) => { 25 | if (~country.city.indexOf(' Division')) { 26 | country.city = country.city.replace(' Division', ''); 27 | } 28 | return country; 29 | }); 30 | }) 31 | ) 32 | } 33 | 34 | getCountries() { 35 | return this.httpClient.jsonp(COUNTRY_API_URL, 'callback'); 36 | } 37 | 38 | getRegions(countryCode: any) { 39 | return this.httpClient 40 | .jsonp(`${REGION_API_ENDPOINT}${countryCode}/all/?key=${BATTUTA_API_KEY}`, 'callback') 41 | .pipe( 42 | map((states: any) => { 43 | return states.map((state: any) => { 44 | if (~state.region.indexOf('State of ') || ~state.region.indexOf('Union Territory of ')) { 45 | state.region = state.region.replace('State of ', '').replace('Union Territory of ', ''); 46 | } 47 | return state; 48 | }) 49 | }) 50 | ) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/app/weather-forecast/services/location.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | 4 | import { of, Observable, BehaviorSubject } from 'rxjs'; 5 | import { tap } from 'rxjs/operators'; 6 | 7 | /* start */ 8 | 9 | const IPDATA_API_ENDPOINT = 'https://api.ipdata.co/1.1.1.1?api-key=150731c1daac573655cad56e9fce2f193d9e5f66a0954e400b465277'; 10 | const FALLBACK_CITY = 'Mumbai'; 11 | const FALLBACK_COUNTRY = 'IN'; 12 | /* end */ 13 | 14 | @Injectable({ 15 | providedIn: 'root' 16 | }) 17 | export class LocationService { 18 | 19 | private ipData$: BehaviorSubject = new BehaviorSubject(null); 20 | ipData: any; 21 | 22 | constructor(private httpClient: HttpClient) { } 23 | 24 | ipDataGetter() { 25 | return this.ipData$.asObservable(); 26 | } 27 | 28 | ipDataSetter(changedIpData: any) { 29 | this.ipData$.next(changedIpData); 30 | } 31 | 32 | getIPData() { 33 | if (this.ipData) { 34 | return of(this.ipData); 35 | } else { 36 | return this.httpClient.jsonp(IPDATA_API_ENDPOINT, 'callback') 37 | .pipe( 38 | tap((ipData: any) => { 39 | // TODO fallback to be removed 40 | if (!ipData.city || ipData.city === 'Ghatkopar') { 41 | ipData.city = FALLBACK_CITY; 42 | ipData.country_code = FALLBACK_COUNTRY; 43 | } 44 | this.ipData$.next(ipData); 45 | this.ipData = ipData; 46 | }) 47 | ) 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/app/weather-forecast/services/weather.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 3 | 4 | 5 | const FORECAST_API_ENDPOINT = 'https://api.openweathermap.org/data/2.5/forecast'; 6 | const OPEN_WEATHER_API_KEY = '7ec24178bacbeb8537ee4d6dbe6724db'; 7 | const FORECAST_API_URL = `${FORECAST_API_ENDPOINT}?appid=${OPEN_WEATHER_API_KEY}`; 8 | 9 | 10 | @Injectable({ 11 | providedIn: 'root' 12 | }) 13 | export class WeatherService { 14 | 15 | constructor(private httpClient: HttpClient) { } 16 | 17 | getWeatherData(city: string, countryCode: string) { 18 | return this.httpClient.get(FORECAST_API_URL, { 19 | params: { q: `${city},${countryCode}`, units: 'metric' } 20 | } 21 | ); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-dashboard/weather-dashboard.component.css: -------------------------------------------------------------------------------- 1 | .dashboar-tab-group { 2 | border: 1px solid #e8e8e8; 3 | } 4 | 5 | .dashboar-tab-content { 6 | padding: 16px; 7 | } 8 | 9 | .temp { 10 | font-size: 20px; 11 | } 12 | 13 | .temp-title { 14 | flex-direction: row; 15 | line-height: 50px; 16 | } 17 | 18 | .description { 19 | line-height: 50px; 20 | } 21 | 22 | .temp-status { 23 | font-size: 25px; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-dashboard/weather-dashboard.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | 5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
-------------------------------------------------------------------------------- /src/app/weather-forecast/weather-dashboard/weather-dashboard.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { CUSTOM_ELEMENTS_SCHEMA, ViewChildren, QueryList } from '@angular/core'; 3 | 4 | import { Observable } from 'rxjs/Observable'; 5 | import { of } from 'rxjs'; 6 | 7 | import { DashboardComponent } from './dashboard.component'; 8 | import { LocationService } from '../services/location.service'; 9 | import { WeatherService } from '../services/weather.service'; 10 | import { WeatherForecastComponent } from '../weather-forecast/weather-forecast.component'; 11 | import { CustomMaterialModule } from '../custom-material/custom-material.module'; 12 | import { AppModule } from '../app.module'; 13 | import { ipData, weatherData } from '../../test/mock-data' 14 | import { BrowserModule, By } from '@angular/platform-browser'; 15 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 16 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 17 | import { MatExpansionPanel } from '@angular/material'; 18 | 19 | describe('Dashboard Component', () => { 20 | let component: DashboardComponent; 21 | let fixture: ComponentFixture; 22 | let weatherService: WeatherService; 23 | let locationService: LocationService; 24 | 25 | beforeEach(() => { 26 | let ipData$ = new BehaviorSubject(ipData); 27 | let stubLocationService = { 28 | getIPData: () => of(ipData), 29 | ipData: ipData, 30 | ipData$: ipData$, 31 | ipDataGetter: () => ipData$.asObservable() 32 | }, stubWeatherService = { 33 | getWeatherData: () => of(weatherData) 34 | }; 35 | 36 | TestBed.configureTestingModule({ 37 | imports: [CustomMaterialModule, NoopAnimationsModule], 38 | declarations: [DashboardComponent], 39 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 40 | providers: [ 41 | { provide: LocationService, useValue: stubLocationService }, 42 | { provide: WeatherService, useValue: stubWeatherService }, 43 | ] 44 | }).compileComponents(); 45 | 46 | fixture = TestBed.createComponent(DashboardComponent); 47 | 48 | component = fixture.componentInstance; 49 | }); 50 | 51 | 52 | describe('Default values', () => { 53 | it('location should have loaded with city, state and country', () => { 54 | //after component loaded 55 | fixture.detectChanges(); 56 | expect(component.locationData).toBeDefined(); 57 | expect(component.locationData.city).toBeDefined(); 58 | expect(component.locationData.region_code).toBeDefined(); 59 | expect(component.locationData.country_code).toBeDefined(); 60 | expect(component.weatherCast).toBeDefined(); 61 | }); 62 | 63 | it('should open first accordion', () => { 64 | fixture.detectChanges(); 65 | expect(component.selected).toBe(0); 66 | }); 67 | }); 68 | 69 | it('Should load single panel', () => { 70 | fixture.detectChanges(); 71 | let el = fixture.nativeElement; 72 | const accordion = el.querySelectorAll('mat-accordion'); 73 | expect(accordion.length).toBe(1); 74 | }); 75 | 76 | it('Should have loaded all accordion on page', () => { 77 | fixture.detectChanges(); 78 | let el = fixture.nativeElement; 79 | const expansionPanel = el.querySelectorAll('mat-expansion-panel'); 80 | expect(expansionPanel.length).toBe(5); 81 | }); 82 | 83 | describe("Should change the index of panel if different panel", function(){ 84 | it('3rd accordion selected', () => { 85 | let el = fixture.nativeElement; 86 | 87 | fixture.detectChanges(); 88 | const expansionPanel = fixture.debugElement.queryAll(By.css('mat-expansion-panel')); 89 | expansionPanel[2].componentInstance.opened.emit(); 90 | 91 | fixture.detectChanges(); 92 | expect(component.selected).toBe(2*8); 93 | }); 94 | 95 | it('5rd accordion selected', () => { 96 | let el = fixture.nativeElement; 97 | 98 | fixture.detectChanges(); 99 | const expansionPanel = fixture.debugElement.queryAll(By.css('mat-expansion-panel')); 100 | expansionPanel[4].componentInstance.opened.emit(); 101 | 102 | fixture.detectChanges(); 103 | expect(component.selected).toBe(4*8); 104 | }); 105 | }); 106 | 107 | 108 | }); 109 | -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-dashboard/weather-dashboard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy, Input } from '@angular/core'; 2 | import { WeatherService } from '../services/weather.service'; 3 | import { LocationService } from '../services/location.service'; 4 | 5 | @Component({ 6 | selector: 'wc-weather-dashboard', 7 | templateUrl: './weather-dashboard.component.html', 8 | styleUrls: ['./weather-dashboard.component.css'] 9 | }) 10 | export class WeatherDashboardComponent implements OnInit, OnDestroy { 11 | 12 | @Input() headingStart = 'Weather Forecast in'; 13 | 14 | weatherCast: any; 15 | locationData: any; 16 | date: Date = new Date(); 17 | selected = 0; 18 | 19 | constructor( 20 | private weatherService: WeatherService, 21 | private locationService: LocationService 22 | ) { } 23 | 24 | 25 | getWeatherData(ipData: any) { 26 | this.weatherService.getWeatherData('MUMBAI', 'IN').subscribe( 27 | weatherCast => { 28 | this.weatherCast = weatherCast 29 | } 30 | ); 31 | } 32 | selectAccordion(index: number){ 33 | this.selected = index; 34 | } 35 | 36 | ngOnInit() { 37 | this.getWeatherData(this.locationData); 38 | } 39 | 40 | ngOnDestroy() { 41 | } 42 | } 43 | 44 | import { NgModule } from '@angular/core'; 45 | import { CommonModule } from '@angular/common'; 46 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 47 | import { WeatherForecastComponent } from '../weather-forecast/weather-forecast.component'; 48 | import { WeatherForecastDetailsComponent } from '../weather-forecast-details/weather-forecast-details.component'; 49 | import { WeatherForecastCityComponent } from '../weather-forecast-city/weather-forecast-city.component'; 50 | import { WeatherForecastHistoryComponent } from '../weather-forecast-history/weather-forecast-history.component'; 51 | import { CustomMaterialModule } from '../../custom-material/custom-material.module'; 52 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 53 | import { FiltersComponent } from '../filters/filters.component'; 54 | 55 | @NgModule({ 56 | declarations: [ 57 | WeatherDashboardComponent, 58 | FiltersComponent, 59 | WeatherForecastComponent, 60 | WeatherForecastDetailsComponent, 61 | WeatherForecastCityComponent, 62 | WeatherForecastHistoryComponent 63 | ], 64 | imports: [ 65 | CommonModule, 66 | BrowserAnimationsModule, 67 | CustomMaterialModule, 68 | FormsModule, 69 | ReactiveFormsModule, 70 | ], 71 | exports: [WeatherDashboardComponent] 72 | }) 73 | export class WeatherForecastModule { } 74 | -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast-city/weather-forecast-city.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | } 6 | 7 | .temp-current { 8 | font-size: 25px; 9 | } 10 | 11 | .temp-status { 12 | font-size: 22px; 13 | } 14 | 15 | .weather-img { 16 | top: 10px; 17 | position: relative; 18 | } -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast-city/weather-forecast-city.component.html: -------------------------------------------------------------------------------- 1 | 3 | {{cast.main.temp}}℃ 4 | - {{cast.weather[0].main}} -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast-city/weather-forecast-city.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { CUSTOM_ELEMENTS_SCHEMA, ViewChildren, QueryList } from '@angular/core'; 3 | 4 | import { Observable } from 'rxjs/Observable'; 5 | 6 | import { LocationService } from '../services/location.service'; 7 | import { CustomMaterialModule } from '../custom-material/custom-material.module'; 8 | import { AppModule } from '../app.module'; 9 | import { weatherData } from '../../test/mock-data' 10 | import { BrowserModule, By } from '@angular/platform-browser'; 11 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 12 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 13 | import { MatExpansionPanel } from '@angular/material'; 14 | import { WeatherForecastCityComponent } from '../weather-forecast-city/weather-forecast-city.component'; 15 | 16 | describe('Weather Forecast City Component', () => { 17 | let component: WeatherForecastCityComponent; 18 | let fixture: ComponentFixture; 19 | let locationService: LocationService; 20 | 21 | beforeEach(() => { 22 | TestBed.configureTestingModule({ 23 | imports: [CustomMaterialModule, NoopAnimationsModule], 24 | declarations: [WeatherForecastCityComponent], 25 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 26 | }).compileComponents(); 27 | 28 | fixture = TestBed.createComponent(WeatherForecastCityComponent); 29 | component = fixture.componentInstance; 30 | let cast = Object.assign({}, weatherData).list[0]; 31 | // cast.weather = undefined; 32 | component.cast = cast; 33 | }); 34 | 35 | 36 | describe('should display accurate output text displayed', () => { 37 | it('should have temperature', () => { 38 | fixture.detectChanges(); 39 | var temp = fixture.debugElement.query(By.css('.temp-current')); 40 | expect(Number(temp.nativeElement.innerText)).toBe(component.cast.main.temp); 41 | }); 42 | it('should have main temperature status', () => { 43 | fixture.detectChanges(); 44 | var tempStatus = fixture.debugElement.query(By.css('.temp-status')); 45 | var weatherPresent = tempStatus.nativeElement.innerText.indexOf(component.cast.weather[0].main); 46 | expect(weatherPresent).toBeGreaterThan(-1); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast-city/weather-forecast-city.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'wc-weather-forecast-city', 5 | templateUrl: './weather-forecast-city.component.html', 6 | styleUrls: ['./weather-forecast-city.component.css'], 7 | changeDetection: ChangeDetectionStrategy.OnPush 8 | }) 9 | export class WeatherForecastCityComponent implements OnInit { 10 | @Input() cast: any; 11 | constructor() { } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast-details/weather-forecast-details.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | } -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast-details/weather-forecast-details.component.html: -------------------------------------------------------------------------------- 1 | 2 | min 3 | 4 | {{cast.main.temp_min | number: '.2-2'}}℃  -  5 | 6 | max 7 | 8 | {{cast.main.temp_max | number: '.2-2'}}℃ 9 | {{cast.clouds.all}}  10 | {{cast.rain['3h']}}  11 | {{cast.wind.main}}  12 | {{cast.dt_txt | date}} -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast-details/weather-forecast-details.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { CUSTOM_ELEMENTS_SCHEMA, ViewChildren, QueryList } from '@angular/core'; 3 | 4 | import { Observable } from 'rxjs/Observable'; 5 | 6 | import { LocationService } from '../services/location.service'; 7 | import { CustomMaterialModule } from '../custom-material/custom-material.module'; 8 | import { weatherData } from '../../test/mock-data' 9 | import { BrowserModule, By } from '@angular/platform-browser'; 10 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 11 | import { WeatherForecastDetailsComponent } from './weather-forecast-details.component'; 12 | 13 | describe('Weather Forecast Details Component', () => { 14 | let component: WeatherForecastDetailsComponent; 15 | let fixture: ComponentFixture; 16 | let locationService: LocationService; 17 | 18 | beforeEach(() => { 19 | TestBed.configureTestingModule({ 20 | imports: [CustomMaterialModule, NoopAnimationsModule], 21 | declarations: [WeatherForecastDetailsComponent], 22 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 23 | }).compileComponents(); 24 | 25 | fixture = TestBed.createComponent(WeatherForecastDetailsComponent); 26 | component = fixture.componentInstance; 27 | component.cast = weatherData.list[0]; 28 | }); 29 | 30 | 31 | describe('should display accurate output text displayed', () => { 32 | it('should have temperature', () => { 33 | let minTemp = fixture.debugElement.queryAll(By.css('.temp')); 34 | fixture.detectChanges(); 35 | expect(minTemp[0].nativeElement.innerText).toBe(component.cast.main.temp_min.toFixed(2)); 36 | }); 37 | it('should have main temperature status', () => { 38 | let maxTemp = fixture.debugElement.queryAll(By.css('.temp')); 39 | fixture.detectChanges(); 40 | expect(maxTemp[1].nativeElement.innerText).toBe(component.cast.main.temp_max.toFixed(2)); 41 | }); 42 | }); 43 | }); -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast-details/weather-forecast-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'wc-weather-forecast-details', 5 | templateUrl: './weather-forecast-details.component.html', 6 | styleUrls: ['./weather-forecast-details.component.css'], 7 | changeDetection: ChangeDetectionStrategy.OnPush 8 | }) 9 | export class WeatherForecastDetailsComponent implements OnInit { 10 | @Input() cast: any; 11 | constructor() { } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast-history/weather-forecast-history.component.css: -------------------------------------------------------------------------------- 1 | .spacer { 2 | flex: 1 1 auto; 3 | } 4 | 5 | .temp-current-small { 6 | font-size: 25px; 7 | } 8 | 9 | .chip-right{ 10 | float: right; 11 | } 12 | 13 | ul { 14 | list-style: none; 15 | padding: 0; 16 | } 17 | 18 | ul li { 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | } 23 | 24 | .custom-chip{ 25 | transition: box-shadow 280ms cubic-bezier(.4,0,.2,1); 26 | display: inline-flex; 27 | padding: 5px 8px; 28 | border-radius: 24px; 29 | align-items: center; 30 | cursor: default; 31 | background-color: #e0e0e0; 32 | color: rgba(0,0,0,.87); 33 | } -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast-history/weather-forecast-history.component.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Details with 3hrs difference

    3 |
  • 4 | Weather London , GB 5 | {{item.main.temp | number: '.2-2'}}℃ 6 | 7 |
    {{item.dt_txt | date: 'hh:mm a'}}
    8 |
  • 9 |
-------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast-history/weather-forecast-history.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'wc-weather-forecast-history', 5 | templateUrl: './weather-forecast-history.component.html', 6 | styleUrls: ['./weather-forecast-history.component.css'] 7 | }) 8 | export class WeatherForecastHistoryComponent implements OnInit { 9 | startIndex: number = 0; 10 | endIndex: number = 0; 11 | 12 | @Input() history: any[] = []; 13 | @Input() 14 | set start(value: number) { 15 | this.startIndex = value; 16 | this.endIndex = !isNaN(value) ? value + 7 : 0; 17 | } 18 | constructor() { } 19 | 20 | ngOnInit() { 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast.module.ts: -------------------------------------------------------------------------------- 1 | // import { NgModule } from '@angular/core'; 2 | // import { CommonModule } from '@angular/common'; 3 | // import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 4 | // import { DashboardComponent } from './dashboard/dashboard.component'; 5 | // import { FiltersComponent } from './filters/filters.component'; 6 | // import { WeatherForecastComponent } from './weather-forecast/weather-forecast.component'; 7 | // import { WeatherForecastDetailsComponent } from './weather-forecast-details/weather-forecast-details.component'; 8 | // import { WeatherForecastCityComponent } from './weather-forecast-city/weather-forecast-city.component'; 9 | // import { WeatherForecastHistoryComponent } from './weather-forecast-history/weather-forecast-history.component'; 10 | // import { CustomMaterialModule } from '../custom-material/custom-material.module'; 11 | // import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 12 | // import { HttpClientJsonpModule, HttpClientModule } from '@angular/common/http'; 13 | 14 | // @NgModule({ 15 | // declarations: [ 16 | // DashboardComponent, 17 | // FiltersComponent, 18 | // WeatherForecastComponent, 19 | // WeatherForecastDetailsComponent, 20 | // WeatherForecastCityComponent, 21 | // WeatherForecastHistoryComponent 22 | // ], 23 | // imports: [ 24 | // CommonModule, 25 | // BrowserAnimationsModule, 26 | // CustomMaterialModule, 27 | // FormsModule, 28 | // ReactiveFormsModule, 29 | // HttpClientModule, 30 | // HttpClientJsonpModule 31 | // ], 32 | // exports: [DashboardComponent] 33 | // }) 34 | // export class WeatherForecastModule { } 35 | -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast/weather-forecast.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/app/weather-forecast/weather-forecast/weather-forecast.component.css -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast/weather-forecast.component.html: -------------------------------------------------------------------------------- 1 | {{ headingStart }}  2 | {{weather?.city.name}}, {{weather?.city.country}}  {{date | date: 'shortDate'}} -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast/weather-forecast.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { CUSTOM_ELEMENTS_SCHEMA, ViewChildren, QueryList } from '@angular/core'; 3 | 4 | import { Observable } from 'rxjs/Observable'; 5 | 6 | import { LocationService } from '../services/location.service'; 7 | import { WeatherService } from '../services/weather.service'; 8 | import { WeatherForecastComponent } from '../weather-forecast/weather-forecast.component'; 9 | import { CustomMaterialModule } from '../custom-material/custom-material.module'; 10 | import { AppModule } from '../app.module'; 11 | import { ipData, weatherData, formatDate } from '../../test/mock-data' 12 | import { BrowserModule, By } from '@angular/platform-browser'; 13 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 14 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 15 | import { MatExpansionPanel } from '@angular/material'; 16 | 17 | describe('Weather Forecast Component', () => { 18 | let component: WeatherForecastComponent; 19 | let fixture: ComponentFixture; 20 | let weatherService: WeatherService; 21 | let locationService: LocationService; 22 | 23 | beforeEach(() => { 24 | TestBed.configureTestingModule({ 25 | imports: [CustomMaterialModule, NoopAnimationsModule], 26 | declarations: [WeatherForecastComponent], 27 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 28 | }).compileComponents(); 29 | 30 | fixture = TestBed.createComponent(WeatherForecastComponent); 31 | component = fixture.componentInstance; 32 | component.date = new Date(); 33 | component.weather = weatherData; 34 | }); 35 | 36 | 37 | describe('should show accurate output text displayed', () => { 38 | it('should have city', () => { 39 | fixture.detectChanges(); 40 | var index = fixture.debugElement.nativeElement.innerText.indexOf(weatherData.city.name); 41 | expect(index).toBeGreaterThan(-1); 42 | }); 43 | it('should have date', () => { 44 | fixture.detectChanges(); 45 | let reformattedDate = formatDate(component.date); 46 | var index = fixture.debugElement.nativeElement.innerText.indexOf(reformattedDate); 47 | expect(index).toBeGreaterThan(-1); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/app/weather-forecast/weather-forecast/weather-forecast.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'wc-weather-forecast', 5 | templateUrl: './weather-forecast.component.html', 6 | styleUrls: ['./weather-forecast.component.css'], 7 | changeDetection: ChangeDetectionStrategy.OnPush 8 | }) 9 | export class WeatherForecastComponent implements OnInit { 10 | 11 | @Input() headingStart = 'Weather in'; 12 | @Input() weather: any; 13 | @Input() date: any; 14 | 15 | constructor() { } 16 | 17 | ngOnInit() { 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/test.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/assets/test.txt -------------------------------------------------------------------------------- /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 --prod` 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/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pankajparkar/lazy-loading-recipes/76426cbdd397ad2a58bd3aad261af77d9a13e4c1/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DemystifyAngularLazyLoading 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 | /** IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | keys(): string[]; 13 | (id: string): T; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting() 21 | ); 22 | // Then we find all the tests. 23 | const context = require.context('./', true, /\.spec\.ts$/); 24 | // And load the modules. 25 | context.keys().map(context); 26 | -------------------------------------------------------------------------------- /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": "es2015", 18 | "module": "es2020", 19 | "lib": [ 20 | "es2018", 21 | "dom" 22 | ] 23 | }, 24 | "angularCompilerOptions": { 25 | "strictInjectionParameters": true, 26 | "strictInputAccessModifiers": true, 27 | "strictTemplates": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.spec.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/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "align": { 8 | "options": [ 9 | "parameters", 10 | "statements" 11 | ] 12 | }, 13 | "array-type": false, 14 | "arrow-return-shorthand": true, 15 | "curly": true, 16 | "deprecation": { 17 | "severity": "warning" 18 | }, 19 | "eofline": true, 20 | "import-blacklist": [ 21 | true, 22 | "rxjs/Rx" 23 | ], 24 | "import-spacing": true, 25 | "indent": { 26 | "options": [ 27 | "spaces" 28 | ] 29 | }, 30 | "max-classes-per-file": false, 31 | "max-line-length": [ 32 | true, 33 | 140 34 | ], 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-console": [ 47 | true, 48 | "debug", 49 | "info", 50 | "time", 51 | "timeEnd", 52 | "trace" 53 | ], 54 | "no-empty": false, 55 | "no-inferrable-types": [ 56 | true, 57 | "ignore-params" 58 | ], 59 | "no-non-null-assertion": true, 60 | "no-redundant-jsdoc": true, 61 | "no-switch-case-fall-through": true, 62 | "no-var-requires": false, 63 | "object-literal-key-quotes": [ 64 | true, 65 | "as-needed" 66 | ], 67 | "quotemark": [ 68 | true, 69 | "single" 70 | ], 71 | "semicolon": { 72 | "options": [ 73 | "always" 74 | ] 75 | }, 76 | "space-before-function-paren": { 77 | "options": { 78 | "anonymous": "never", 79 | "asyncArrow": "always", 80 | "constructor": "never", 81 | "method": "never", 82 | "named": "never" 83 | } 84 | }, 85 | "typedef": [ 86 | true, 87 | "call-signature" 88 | ], 89 | "typedef-whitespace": { 90 | "options": [ 91 | { 92 | "call-signature": "nospace", 93 | "index-signature": "nospace", 94 | "parameter": "nospace", 95 | "property-declaration": "nospace", 96 | "variable-declaration": "nospace" 97 | }, 98 | { 99 | "call-signature": "onespace", 100 | "index-signature": "onespace", 101 | "parameter": "onespace", 102 | "property-declaration": "onespace", 103 | "variable-declaration": "onespace" 104 | } 105 | ] 106 | }, 107 | "variable-name": { 108 | "options": [ 109 | "ban-keywords", 110 | "check-format", 111 | "allow-pascal-case" 112 | ] 113 | }, 114 | "whitespace": { 115 | "options": [ 116 | "check-branch", 117 | "check-decl", 118 | "check-operator", 119 | "check-separator", 120 | "check-type", 121 | "check-typecast" 122 | ] 123 | }, 124 | "component-class-suffix": true, 125 | "contextual-lifecycle": true, 126 | "directive-class-suffix": true, 127 | "no-conflicting-lifecycle": true, 128 | "no-host-metadata-property": true, 129 | "no-input-rename": true, 130 | "no-inputs-metadata-property": true, 131 | "no-output-native": true, 132 | "no-output-on-prefix": true, 133 | "no-output-rename": true, 134 | "no-outputs-metadata-property": true, 135 | "template-banana-in-box": true, 136 | "template-no-negated-async": true, 137 | "use-lifecycle-interface": true, 138 | "use-pipe-transform-interface": true, 139 | "directive-selector": [ 140 | true, 141 | "attribute", 142 | "app", 143 | "camelCase" 144 | ], 145 | "component-selector": [ 146 | true, 147 | "element", 148 | "app", 149 | "kebab-case" 150 | ] 151 | } 152 | } 153 | --------------------------------------------------------------------------------