19

I want to be able to create a popup window which will load a certain Angular 4 component of mine when a radio button is selected.

It seems that the methods listed in the answers to this question are only compatible with Angular 2.

I am not sure where to begin and would appreciate any help!

Community
  • 1
  • 1
Luca Guarro
  • 1,085
  • 1
  • 11
  • 25

2 Answers2

21

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.

Ron Newcomb
  • 2,886
  • 21
  • 24
  • 5
    I really like this answer because it is lightweight and flexible, and it's written to teach you something instead of using a huge framework to accomplish a tiny task. – MaxAxeHax Mar 20 '18 at 13:20
  • I'm trying to follow this code as well, but don't understand who should catch the emitted event? Does some part of the parent HTML handle this? –  Apr 25 '18 at 12:05
  • parent's HTML has `` which is where the child's emitted `close` event is caught and routed to the parent controller's closeModal function. – Ron Newcomb Apr 25 '18 at 21:30
  • To be honest, knowing we can use angular material to achieve this task is almost useless, if not harmful, to me. However what this answer lacks is that normally people won't use modal as a embedded component, instead they are always used as a service, ex. `modalObject.popup()` where this `modalObject` can be initialized on the fly. Therefore it should have no trace of this component in the aplication template. – windmaomao Apr 26 '18 at 14:21
  • love this answer. Simple, customizable and not dependant on other libraries.. Thanks.. – Matrim Jan 03 '20 at 10:52
  • @windmaomao I was surprised to learn a lot of React devs prefer the above inline approach. I wrote about when to prefer one over the other https://dev.to/ronnewcomb/modal-dialogs-as-a-promise-versus-inline-1d4l – Ron Newcomb Aug 13 '21 at 04:00
13

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>) {}
}
Madhu Ranjan
  • 17,334
  • 7
  • 60
  • 69
  • I am calling the openDialog() function in a sub-component rather than directly in the root so I do not want to bootstrap the dialog component in the main.ts (as this gives me a plethora of errors saying that this component did not match any elements). Would I need to give the sub-component something like its own module.ts? How do I resolve this? – Luca Guarro Apr 21 '17 at 04:14
  • MdDialog is part of Angular Material component suite, read [getting started guide](https://material.angular.io/guide/getting-started) to set that up, it is as simple as installi material npm lib and including in your main module, Cheers!! – Madhu Ranjan Apr 21 '17 at 04:22
  • 2
    Thank you. With a bit of searching around, I was able to find the solution. I had to add "entryComponents: [DialogResultExampleDialog]" Instead of bootstrapping it – Luca Guarro Apr 21 '17 at 04:32
  • 1
    this doesnt work.Cannot find module '@angular/material'. –  May 15 '17 at 05:39
  • @ritesh, did you npm install angular material? check this [getting started guide with Angular material](https://material.angular.io/guide/getting-started) – Madhu Ranjan May 15 '17 at 14:10
  • 2
    The plunker fails to load for me. – pauloya Jul 25 '17 at 09:43
  • 1
    @pauloya, thnks for letting me know, updated the URL, Cheers!! – Madhu Ranjan Jul 26 '17 at 13:11
  • I am getting this error in browser (compile is OK) using your solution: Uncaught Error: Can't resolve all parameters for MdDialogRef: (?, ?) – Al007 Aug 27 '17 at 15:12
  • I'm using @angular/core (and all other @angular packages in version 4.2.5). So I tried ```npm install @angular/material@4.2.5```; it does not exists. With ```npm view @angular/material versions --json``` you can see that it jumps from version 2.x to 5.x. It does not exists in version 4 at all. Which one it is suppoesed to use for Angular4 ? – Alex 75 Aug 05 '18 at 09:27