<!DOCTYPE html>
<html>
<head>
<base href="./" />
<title>Angular 2 Custom Modal</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- application less -->
<link href="app/_content/modal.less" rel="stylesheet/less" type="text/css" />
<link href="app/_content/app.less" rel="stylesheet/less" type="text/css" />
<!-- compiling less on client side for the example only, this should be done on the server in a production app -->
<script src="//cdnjs.cloudflare.com/ajax/libs/less.js/2.5.3/less.min.js"></script>
<!-- polyfill(s) for older browsers -->
<script src="https://unpkg.com/core-js/client/shim.min.js"></script>
<script src="https://unpkg.com/zone.js@0.6.23?main=browser"></script>
<script src="https://unpkg.com/reflect-metadata@0.1.3"></script>
<script src="https://unpkg.com/systemjs@0.19.27/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function (err) { console.error(err); });
</script>
</head>
<body>
<app>Loading...</app>
</body>
</html>
/**
* WEB ANGULAR VERSION
* (based on systemjs.config.js in angular.io)
* System configuration for Angular samples
* Adjust as necessary for your application needs.
*/
(function (global) {
System.config({
// DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
transpiler: 'ts',
typescriptOptions: {
// Complete copy of compiler options in standard tsconfig.json
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"typeRoots": [
"../../node_modules/@types/"
]
},
meta: {
'typescript': {
"exports": "ts"
}
},
paths: {
// paths serve as alias
'npm:': 'https://unpkg.com/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
app: 'app',
// angular bundles
'@angular/common': 'npm:@angular/common@2.2.1/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler@2.2.1/bundles/compiler.umd.js',
'@angular/core': 'npm:@angular/core@2.2.1/bundles/core.umd.js',
'@angular/forms': 'npm:@angular/forms@2.2.1/bundles/forms.umd.js',
'@angular/http': 'npm:@angular/http@2.2.1/bundles/http.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser@2.2.1/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@2.2.1/bundles/platform-browser-dynamic.umd.js',
'@angular/router': 'npm:@angular/router@3.2.1/bundles/router.umd.js',
// other libraries
'rxjs': 'npm:rxjs@5.0.0-rc.3',
'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js',
'typescript': 'npm:typescript@2.0.3/lib/typescript.js',
'underscore': 'npm:underscore@1.8.3',
'jquery': 'npm:jquery@3.1.1'
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.ts',
defaultExtension: 'ts'
},
rxjs: {
defaultExtension: 'js'
}
}
});
if (!global.noBootstrap) { bootstrap(); }
// Bootstrap the `AppModule`(skip the `app/main.ts` that normally does this)
function bootstrap() {
// Stub out `app/main.ts` so System.import('app') doesn't fail if called in the index.html
System.set(System.normalizeSync('app/main.ts'), System.newModule({ }));
// bootstrap and launch the app (equivalent to standard main.ts)
Promise.all([
System.import('@angular/platform-browser-dynamic'),
System.import('app/app.module')
])
.then(function (imports) {
var platform = imports[0];
var app = imports[1];
platform.platformBrowserDynamic().bootstrapModule(app.AppModule);
})
.catch(function(err){ console.error(err); });
}
})(this);
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
import { Component } from '@angular/core';
@Component({
moduleId: module.id.toString(),
selector: 'app',
templateUrl: 'app.component.html'
})
export class AppComponent { }
<!-- main app container -->
<div>
<nav>
<a [routerLink]="['/']">Home</a>
<a [routerLink]="['/test-page']">Test Page</a>
</nav>
<router-outlet></router-outlet>
</div>
<!-- credits -->
<div class="credits">
<p>
<a href="http://jasonwatmore.com/post/2017/01/24/angular-2-custom-modal-window-dialog-box" target="_top">Angular 2 - Custom Modal Window / Dialog Box</a>
</p>
<p>
<a href="http://jasonwatmore.com" target="_top">JasonWatmore.com</a>
</p>
</div>
export * from './modal.service';
import * as _ from 'underscore';
export class ModalService {
private modals: any[] = [];
add(modal: any) {
// add modal to array of active modals
this.modals.push(modal);
}
remove(id: string) {
// remove modal from array of active modals
let modalToRemove = _.findWhere(this.modals, { id: id });
this.modals = _.without(this.modals, modalToRemove);
}
open(id: string) {
// open modal specified by id
let modal = _.findWhere(this.modals, { id: id });
modal.open();
}
close(id: string) {
// close modal specified by id
let modal = _.find(this.modals, { id: id });
modal.close();
}
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { routing } from './app.routing';
import { ModalComponent } from './_directives/index';
import { ModalService } from './_services/index';
import { HomeComponent } from './home/index';
import { TestPageComponent } from './test-page/index';
@NgModule({
imports: [
BrowserModule,
FormsModule,
routing
],
declarations: [
AppComponent,
ModalComponent,
HomeComponent,
TestPageComponent
],
providers: [
ModalService
],
bootstrap: [AppComponent]
})
export class AppModule { }
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/index';
import { TestPageComponent } from './test-page/index';
const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'test-page', component: TestPageComponent },
// otherwise redirect to home
{ path: '**', redirectTo: '' }
];
export const routing = RouterModule.forRoot(appRoutes);
import { Component } from '@angular/core';
@Component({
moduleId: module.id.toString(),
templateUrl: 'test-page.component.html'
})
export class TestPageComponent {
}
<div class="col-md-6 col-md-offset-3">
<h1>Test Page</h1>
<p>This one doesn't have any modals...</p>
</div>
export * from './test-page.component';
import { Component, OnInit } from '@angular/core';
import { ModalService } from '../_services/index';
@Component({
moduleId: module.id.toString(),
templateUrl: 'home.component.html'
})
export class HomeComponent implements OnInit {
private bodyText: string;
constructor(private modalService: ModalService) {
}
ngOnInit() {
this.bodyText = 'This text can be updated in modal 1';
}
openModal(id: string){
this.modalService.open(id);
}
closeModal(id: string){
this.modalService.close(id);
}
}
<div class="col-md-6 col-md-offset-3">
<h1>Home</h1>
<p>{{bodyText}}</p>
<button (click)="openModal('custom-modal-1')">Open Modal 1</button>
<button (click)="openModal('custom-modal-2')">Open Modal 2</button>
</div>
<modal id="custom-modal-1">
<div class="modal">
<div class="modal-body">
<h1>A Custom Modal!</h1>
<p>
Home page text: <input type="text" [(ngModel)]="bodyText" />
</p>
<button (click)="closeModal('custom-modal-1');">Close</button>
</div>
</div>
<div class="modal-background"></div>
</modal>
<modal id="custom-modal-2">
<div class="modal">
<div class="modal-body">
<h1 style="height:1000px">A Tall Custom Modal!</h1>
<button (click)="closeModal('custom-modal-2');">Close</button>
</div>
</div>
<div class="modal-background"></div>
</modal>
export * from './home.component';
import { Component, ElementRef, Input, OnInit, OnDestroy } from '@angular/core';
import * as $ from 'jquery';
import { ModalService } from '../_services/index';
@Component({
moduleId: module.id.toString(),
selector: 'modal',
template: '<ng-content></ng-content>'
})
export class ModalComponent implements OnInit, OnDestroy {
@Input() id: string;
private element: JQuery;
constructor(private modalService: ModalService, private el: ElementRef) {
this.element = $(el.nativeElement);
}
ngOnInit(): void {
let modal = this;
// ensure id attribute exists
if (!this.id) {
console.error('modal must have an id');
return;
}
// move element to bottom of page (just before </body>) so it can be displayed above everything else
this.element.appendTo('body');
// close modal on background click
this.element.on('click', function (e: any) {
var target = $(e.target);
if (!target.closest('.modal-body').length) {
modal.close();
}
});
// add self (this modal instance) to the modal service so it's accessible from controllers
this.modalService.add(this);
}
// remove self from modal service when directive is destroyed
ngOnDestroy(): void {
this.modalService.remove(this.id);
this.element.remove();
}
// open modal
open(): void {
this.element.show();
$('body').addClass('modal-open');
}
// close modal
close(): void {
this.element.hide();
$('body').removeClass('modal-open');
}
}
export * from './modal.component';
/* EXAMPLE STYLES
-------------------------------*/
body {
font-family: roboto;
padding: 20px;
}
nav {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #ddd;
a {
margin-right: 8px;
}
}
h1 {
font-weight: normal;
margin-top: 0;
}
input[type="text"] {
display:block;
width: 100%;
font-family: roboto;
}
.credits {
margin-top: 30px;
border-top: 1px solid #ddd;
text-align: center;
}
/* MODAL STYLES
-------------------------------*/
modal {
/* modals are hidden by default */
display: none;
.modal {
/* modal container fixed across whole screen */
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
/* z-index must be higher than .modal-background */
z-index: 1000;
/* enables scrolling for tall modals */
overflow: auto;
.modal-body {
padding: 20px;
background: #fff;
/* margin exposes part of the modal background */
margin: 40px;
}
}
.modal-background {
/* modal background fixed across whole screen */
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
/* semi-transparent black */
background-color: #000;
opacity: 0.75;
/* z-index must be below .modal and above everything else */
z-index: 900;
}
}
body.modal-open {
/* body overflow is hidden to hide main scrollbar when modal window is open */
overflow: hidden;
}