How to create a modal popup that is compatible with Angular 4
The accepted answer adds a large dependency to swat a fly. Modal (and modeless) dialogs are largely the result of a CSS class or two. Try this "rename..." example:
1) Write the parent and child-modal as if the child wasn't modal at all, but just an inline form with *ngIf attached.
Parent HTML that uses <my-modal>
child:
<div> A div for {{name}}. <button type="button" (click)="showModal()">Rename</button> <my-modal *ngIf="showIt" [oldname]="name" (close)="closeModal($event)"></my-modal></div>
Parent class. The @Component
decorator omitted for brevity. (The name
property belongs to the parent class and would exist even if we didn't have a form to alter it.)
export class AppComponent { name = "old name"; showIt = false; showModal() { this.showIt = true; } closeModal(newName: string) { this.showIt = false; if (newName) this.name = newName; }}
Child to-be-modal component. @Component
decorator and imports again omitted.
export class MyModalComponent { @Input() oldname = ""; @Output() close = new EventEmitter<string>(); newname = ""; ngOnInit() { // copy all inputs to avoid polluting them this.newname = this.oldname; } ok() { this.close.emit(this.newname); } cancel() { this.close.emit(null); }}
Child HTML before modal-izing it.
<div> Rename {{oldname}} <input type="text" (change)="newname = $event.target.value;" /> <button type="button" (click)="ok()">OK</button> <button type="button" (click)="cancel()">Cancel</button></div>
2) Here's the CSS for child, but it can be placed in a global stylesheet for re-use throughout your app. It's a single class called modal
and is intended for a <div>
element.
.modal { /* detach from rest of the document */ position: fixed; /* center */ left: 50%; top: 50%; transform: translate(-50%, -50%); /* ensure in front of rest of page -- increase as needed */ z-index: 1001; /* visual illusion of being in front -- alter to taste */ box-shadow: rgba(0,0,0,0.4) 10px 10px 4px; /* visual illusion of being a solid object -- alter to taste */ background-color: lightblue; border: 5px solid darkblue; /* visual preference of don't crowd the contents -- alter to taste */ padding: 10px;}
But the modal
CSS class won't prevent interacting with the page underneath it. (So it technically creates a modeless dialog.) So we place an overlay
underneath the modal to absorb and ignore mouse activity. overlay
is also intended for a <div>
element.
.overlay { /* detach from document */ position: fixed; /* ensure in front of rest of page except modal */ z-index: 1000; /* fill screen to catch mice */ top: 0; left: 0; width: 9999px; height: 9999px; /* dim screen 20% -- alter to taste */ opacity: 0.2; background-color: black;}
3) Use the modal
and overlay
in the child HTML.
<div class="modal"> Rename {{oldname}} <input type="text" (change)="newname = $event.target.value;" /> <button type="button" (click)="ok()">OK</button> <button type="button" (click)="cancel()">Cancel</button></div><div class="overlay"></div>
And that's it. Basically 2 CSS classes and you can make any component a modal. In fact you can show a component in-line or as a modal at run-time just by altering the existance of the CSS class with ngClass
or [class.modal]="showAsModalBoolean"
.
You can alter this so the child controls the show/hide logic. Move the *ngIf, showIt, and show() function into the child. In the parent add @ViewChild(MyModalComponent) renameModal: MyModalComponent;
and then the parent can imperatively call this.renameModal.show(this.name);
and re-wire initialization and containing divs as needed.
The child-modal can return info to a parent's function as shown above, or the child's show()
method could instead accept a callback or return a Promise, as per taste.
Two things to know:
this.renameModal.show(..);
won't work if there's an *ngIf on <my-modal>
because it won't exist to expose the function to begin with. *ngIf removes the whole component, show() function and all, so use [hidden]
instead if you need this for some reason.
Modals-on-modals will have z-index issues since they all share the same z-index. This can be solved with [style.z-index]="calculatedValue"
or similar.
Check Angular Material Dialogue, here is the Plunker
import {Component} from '@angular/core';import {MdDialog, MdDialogRef} from '@angular/material';@Component({ selector: 'dialog-result-example', templateUrl: './dialog-result-example.html',})export class DialogResultExample { selectedOption: string; constructor(public dialog: MdDialog) {} openDialog() { let dialogRef = this.dialog.open(DialogResultExampleDialog); dialogRef.afterClosed().subscribe(result => { this.selectedOption = result; }); }}@Component({ selector: 'dialog-result-example-dialog', templateUrl: './dialog-result-example-dialog.html',})export class DialogResultExampleDialog { constructor(public dialogRef: MdDialogRef<DialogResultExampleDialog>) {}}