├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── app │ ├── _components │ │ ├── index.ts │ │ └── modal.component.ts │ ├── _content │ │ ├── app.less │ │ └── modal.less │ ├── _services │ │ ├── index.ts │ │ └── modal.service.ts │ ├── app.component.html │ ├── app.component.ts │ ├── app.module.ts │ ├── app.routing.ts │ ├── home │ │ ├── home.component.html │ │ ├── home.component.ts │ │ └── index.ts │ └── test-page │ │ ├── index.ts │ │ ├── test-page.component.html │ │ └── test-page.component.ts ├── index.html ├── main.ts └── polyfills.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | typings 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | # Generated files 41 | app/**/*.js 42 | app/**/*.js.map -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Jason Watmore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-7-custom-modal 2 | 3 | Angular 7 Custom Modal with Webpack 4 4 | 5 | Demo and documentation at http://jasonwatmore.com/post/2019/04/16/angular-7-custom-modal-window-dialog-box -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-7-custom-modal", 3 | "version": "1.0.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/cornflourblue/angular-7-custom-modal.git" 7 | }, 8 | "scripts": { 9 | "build": "webpack --mode production", 10 | "start": "webpack-dev-server --mode development --open" 11 | }, 12 | "license": "MIT", 13 | "dependencies": { 14 | "@angular/common": "^7.0.0", 15 | "@angular/compiler": "^7.0.0", 16 | "@angular/core": "^7.0.0", 17 | "@angular/forms": "^7.0.0", 18 | "@angular/platform-browser": "^7.0.0", 19 | "@angular/platform-browser-dynamic": "^7.0.0", 20 | "@angular/router": "^7.0.0", 21 | "core-js": "^3.0.0", 22 | "rxjs": "^6.0.0", 23 | "zone.js": "^0.8.26" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^11.0.0", 27 | "angular2-template-loader": "^0.6.2", 28 | "css-loader": "^2.1.1", 29 | "html-webpack-plugin": "^3.2.0", 30 | "less": "^3.0.4", 31 | "less-loader": "^4.1.0", 32 | "raw-loader": "^1.0.0", 33 | "style-loader": "^0.23.1", 34 | "ts-loader": "^5.0.0", 35 | "typescript": "^3.0.0", 36 | "webpack": "^4.30.0", 37 | "webpack-cli": "^3.3.0", 38 | "webpack-dev-server": "^3.3.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/_components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './modal.component'; -------------------------------------------------------------------------------- /src/app/_components/modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, Input, OnInit, OnDestroy } from '@angular/core'; 2 | 3 | import { ModalService } from '../_services'; 4 | 5 | @Component({ 6 | selector: 'jw-modal', 7 | template: 8 | `
9 |
10 | 11 |
12 |
13 |
` 14 | }) 15 | export class ModalComponent implements OnInit, OnDestroy { 16 | @Input() id: string; 17 | private element: any; 18 | 19 | constructor(private modalService: ModalService, private el: ElementRef) { 20 | this.element = el.nativeElement; 21 | } 22 | 23 | ngOnInit(): void { 24 | let modal = this; 25 | 26 | // ensure id attribute exists 27 | if (!this.id) { 28 | console.error('modal must have an id'); 29 | return; 30 | } 31 | 32 | // move element to bottom of page (just before ) so it can be displayed above everything else 33 | document.body.appendChild(this.element); 34 | 35 | // close modal on background click 36 | this.element.addEventListener('click', function (e: any) { 37 | if (e.target.className === 'jw-modal') { 38 | modal.close(); 39 | } 40 | }); 41 | 42 | // add self (this modal instance) to the modal service so it's accessible from controllers 43 | this.modalService.add(this); 44 | } 45 | 46 | // remove self from modal service when component is destroyed 47 | ngOnDestroy(): void { 48 | this.modalService.remove(this.id); 49 | this.element.remove(); 50 | } 51 | 52 | // open modal 53 | open(): void { 54 | this.element.style.display = 'block'; 55 | document.body.classList.add('jw-modal-open'); 56 | } 57 | 58 | // close modal 59 | close(): void { 60 | this.element.style.display = 'none'; 61 | document.body.classList.remove('jw-modal-open'); 62 | } 63 | } -------------------------------------------------------------------------------- /src/app/_content/app.less: -------------------------------------------------------------------------------- 1 | /* EXAMPLE STYLES 2 | -------------------------------*/ 3 | body { 4 | font-family: Arial, Helvetica, sans-serif; 5 | padding: 20px; 6 | } 7 | 8 | nav { 9 | margin-bottom: 20px; 10 | padding-bottom: 20px; 11 | border-bottom: 1px solid #ddd; 12 | 13 | a { 14 | margin-right: 8px; 15 | } 16 | } 17 | 18 | h1 { 19 | font-weight: normal; 20 | margin-top: 0; 21 | } 22 | 23 | input[type="text"] { 24 | display:block; 25 | width: 100%; 26 | font-family: Arial, Helvetica, sans-serif; 27 | } 28 | 29 | .credits { 30 | margin-top: 30px; 31 | border-top: 1px solid #ddd; 32 | text-align: center; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/app/_content/modal.less: -------------------------------------------------------------------------------- 1 | /* MODAL STYLES 2 | -------------------------------*/ 3 | jw-modal { 4 | /* modals are hidden by default */ 5 | display: none; 6 | 7 | .jw-modal { 8 | /* modal container fixed across whole screen */ 9 | position: fixed; 10 | top: 0; 11 | right: 0; 12 | bottom: 0; 13 | left: 0; 14 | 15 | /* z-index must be higher than .jw-modal-background */ 16 | z-index: 1000; 17 | 18 | /* enables scrolling for tall modals */ 19 | overflow: auto; 20 | 21 | .jw-modal-body { 22 | padding: 20px; 23 | background: #fff; 24 | 25 | /* margin exposes part of the modal background */ 26 | margin: 40px; 27 | } 28 | } 29 | 30 | .jw-modal-background { 31 | /* modal background fixed across whole screen */ 32 | position: fixed; 33 | top: 0; 34 | right: 0; 35 | bottom: 0; 36 | left: 0; 37 | 38 | /* semi-transparent black */ 39 | background-color: #000; 40 | opacity: 0.75; 41 | 42 | /* z-index must be below .jw-modal and above everything else */ 43 | z-index: 900; 44 | } 45 | } 46 | 47 | body.jw-modal-open { 48 | /* body overflow is hidden to hide main scrollbar when modal window is open */ 49 | overflow: hidden; 50 | } 51 | -------------------------------------------------------------------------------- /src/app/_services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './modal.service'; 2 | -------------------------------------------------------------------------------- /src/app/_services/modal.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ providedIn: 'root' }) 4 | export class ModalService { 5 | private modals: any[] = []; 6 | 7 | add(modal: any) { 8 | // add modal to array of active modals 9 | this.modals.push(modal); 10 | } 11 | 12 | remove(id: string) { 13 | // remove modal from array of active modals 14 | this.modals = this.modals.filter(x => x.id !== id); 15 | } 16 | 17 | open(id: string) { 18 | // open modal specified by id 19 | let modal: any = this.modals.filter(x => x.id === id)[0]; 20 | modal.open(); 21 | } 22 | 23 | close(id: string) { 24 | // close modal specified by id 25 | let modal: any = this.modals.filter(x => x.id === id)[0]; 26 | modal.close(); 27 | } 28 | } -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |  2 |
3 | 7 | 8 |
9 | 10 | 11 |
12 |

13 | Angular 7 - Custom Modal Window / Dialog Box 14 |

15 |

16 | JasonWatmore.com 17 |

18 |
19 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import './_content/app.less'; 4 | import './_content/modal.less'; 5 | 6 | @Component({ selector: 'app', templateUrl: 'app.component.html' }) 7 | export class AppComponent { } -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { AppComponent } from './app.component'; 6 | import { routing } from './app.routing'; 7 | 8 | import { ModalComponent } from './_components'; 9 | import { HomeComponent } from './home'; 10 | import { TestPageComponent } from './test-page'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | BrowserModule, 15 | FormsModule, 16 | routing 17 | ], 18 | declarations: [ 19 | AppComponent, 20 | ModalComponent, 21 | HomeComponent, 22 | TestPageComponent 23 | ], 24 | bootstrap: [AppComponent] 25 | }) 26 | 27 | export class AppModule { } -------------------------------------------------------------------------------- /src/app/app.routing.ts: -------------------------------------------------------------------------------- 1 | import { Routes, RouterModule } from '@angular/router'; 2 | 3 | import { HomeComponent } from './home'; 4 | import { TestPageComponent } from './test-page'; 5 | 6 | const appRoutes: Routes = [ 7 | { path: '', component: HomeComponent }, 8 | { path: 'test-page', component: TestPageComponent }, 9 | 10 | // otherwise redirect to home 11 | { path: '**', redirectTo: '' } 12 | ]; 13 | 14 | export const routing = RouterModule.forRoot(appRoutes); -------------------------------------------------------------------------------- /src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 | 
2 |

Home

3 |

{{bodyText}}

4 | 5 | 6 |
7 | 8 | 9 |

A Custom Modal!

10 |

Home page text:

11 | 12 |
13 | 14 | 15 |

A Tall Custom Modal!

16 | 17 |
-------------------------------------------------------------------------------- /src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { ModalService } from '../_services'; 4 | 5 | @Component({ templateUrl: 'home.component.html' }) 6 | export class HomeComponent implements OnInit { 7 | bodyText: string; 8 | 9 | constructor(private modalService: ModalService) { 10 | } 11 | 12 | ngOnInit() { 13 | this.bodyText = 'This text can be updated in modal 1'; 14 | } 15 | 16 | openModal(id: string) { 17 | this.modalService.open(id); 18 | } 19 | 20 | closeModal(id: string) { 21 | this.modalService.close(id); 22 | } 23 | } -------------------------------------------------------------------------------- /src/app/home/index.ts: -------------------------------------------------------------------------------- 1 | export * from './home.component'; -------------------------------------------------------------------------------- /src/app/test-page/index.ts: -------------------------------------------------------------------------------- 1 | export * from './test-page.component'; -------------------------------------------------------------------------------- /src/app/test-page/test-page.component.html: -------------------------------------------------------------------------------- 1 | 
2 |

Test Page

3 |

This one doesn't have any modals...

4 |
-------------------------------------------------------------------------------- /src/app/test-page/test-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ templateUrl: 'test-page.component.html' }) 4 | export class TestPageComponent { } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Angular 7 - Custom Modal Example & Tutorial 6 | 7 | 8 | 9 | Loading... 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills'; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { AppModule } from './app/app.module'; 5 | platformBrowserDynamic().bootstrapModule(AppModule); -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | import 'core-js/features/reflect'; 2 | import 'zone.js/dist/zone'; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "target": "es5" 6 | } 7 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | 3 | module.exports = { 4 | entry: './src/main.ts', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.ts$/, 9 | use: ['ts-loader', 'angular2-template-loader'], 10 | }, 11 | { 12 | test: /\.(html|css)$/, 13 | use: 'raw-loader' 14 | }, 15 | { 16 | test: /\.less$/, 17 | use: [ 18 | { loader: 'style-loader' }, 19 | { loader: 'css-loader' }, 20 | { loader: 'less-loader' } 21 | ] 22 | } 23 | ] 24 | }, 25 | resolve: { 26 | extensions: ['.ts', '.js'] 27 | }, 28 | plugins: [ 29 | new HtmlWebpackPlugin({ template: './src/index.html' }) 30 | ], 31 | optimization: { 32 | splitChunks: { 33 | chunks: 'all', 34 | }, 35 | runtimeChunk: true 36 | }, 37 | devServer: { 38 | historyApiFallback: true 39 | } 40 | }; --------------------------------------------------------------------------------