Skip to content

Instantly share code, notes, and snippets.

@MikeRyanDev
Last active December 18, 2016 20:10
Show Gist options
  • Save MikeRyanDev/cf19e9314cff9143286f to your computer and use it in GitHub Desktop.
Save MikeRyanDev/cf19e9314cff9143286f to your computer and use it in GitHub Desktop.
/**
* A proof-of-concept for a new modal lib for Angular 2
*
* Goals:
* - Easy to style
* - Easy to control with @ngrx/store
* - Completely stateless
*
* How to use:
* In your root component, add `ModalConnector` to your providers array
* and `ModalOutlet` to your directives array. In your template add the
* `<modal-outlet></modal-outlet>` component.
*
* Anywhere else in your app, add `Modal` to your directives array and use
* the `<modal [open]="true" (dismiss)="handle()"></modal>` component.
*
* `[open]` determines whether or not the modal should be shown. `(dismiss)`
* is emitted when the user clicks on the backdrop
*/
import 'rxjs/add/operator/filter';
import {
Renderer,
ElementRef,
AfterViewInit,
OnDestroy,
Input,
Output,
EventEmitter,
Component,
HostBinding,
ViewQuery,
QueryList,
ChangeDetectionStrategy,
ChangeDetectorRef
} from 'angular2/core';
import { BehaviorSubject } from 'rxjs/subject/BehaviorSubject';
import { ReplaySubject } from 'rxjs/subject/ReplaySubject';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
export class ModalConnector {
outlet: ModalOutlet;
backlog: Modal[] = [];
attachOutlet(outlet: ModalOutlet) {
this.outlet = outlet;
this.flushBacklog();
}
detachOutlet() {
this.outlet = undefined;
}
private flushBacklog() {
const backlog = this.backlog;
this.backlog = [];
backlog.forEach(modal => this.addModal(modal));
}
addModal(modal: Modal) {
if( !!this.outlet ) {
this.outlet.accept(modal);
}
else {
this.backlog = [...this.backlog, modal];
}
}
removeModal(modal: Modal) {
if( !!this.outlet ) {
this.outlet.remove(modal);
}
}
}
@Component({
selector: 'modal-outlet',
template: `
<div class="modal-wrapper" [class.show]="show" [ngClass]="wrapperClasses">
<div class="modal-backdrop" (click)="dismiss($event)" #backdrop> </div>
</div>
`,
styles: [`
.modal-wrapper {
display: block;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
width: 100%;
height: 100%;
visibility: hidden;
z-index: 1000;
transition: visibility 300ms;
}
.modal-wrapper.show {
visibility: visible;
}
.modal-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
background-color: black;
opacity: 0;
transition: opacity 300ms;
}
.modal-wrapper.show .modal-backdrop {
position: absolute;
top: 0;
left: 0;
opacity: 0.7;
}
`]
})
export class ModalOutlet implements AfterViewInit, OnDestroy {
constructor(
private connector: ModalConnector,
private renderer: Renderer,
private ref: ElementRef,
@ViewQuery('backdrop') private backdrops: QueryList<ElementRef>
) { }
modals: Modal[] = [];
get show() {
return !!this.modals.find(modal => modal.open);
}
get wrapperClasses() {
return this.modals
.filter(modal => modal.open && !!modal.wrapperClass)
.map(modal => modal.wrapperClass);
}
ngAfterViewInit() {
this.connector.attachOutlet(this);
}
ngOnDestroy() {
this.connector.detachOutlet();
}
get targetNode(): HTMLElement {
return !!this.backdrops.first ? this.backdrops.first.nativeElement : undefined;
}
accept(modal: Modal) {
this.modals = [...this.modals, modal];
this.renderer.attachViewAfter(this.targetNode, [ modal.node ]);
}
remove(modal: Modal) {
this.modals = this.modals.filter(m => m !== modal);
this.renderer.detachView([ modal.node ]);
}
dismiss($event) {
this.modals
.filter(modal => modal.open)
.forEach(modal => modal.dismiss.emit($event));
}
}
@Component({
selector: 'modal',
template: `
<div class="modal" #modal [class.show]="open" [class.useDefault]="useDefaultStyles">
<ng-content></ng-content>
</div>
`,
styles: [`
.modal.useDefault {
position: absolute;
width: 400px;
height: 320px;
top: 50%;
margin-top: -160px;
left: 50%;
margin-left: -200px;
background-color: white;
transform: scale(0.3);
opacity: 0;
transition: transform 300ms, opacity 300ms;
}
.modal.useDefault.show {
transform: scale(1);
opacity: 1.0;
}
`]
})
export class Modal implements AfterViewInit, OnDestroy {
@Output() dismiss = new EventEmitter();
@Input() wrapperClass: string;
@HostBinding('class.useDefault') @Input() useDefaultStyles = true;
@HostBinding('class.show') @Input() open = false;
constructor(
private _ref: ElementRef,
private _connector: ModalConnector,
@ViewQuery('modal') private modalList: QueryList<ElementRef>
) { }
get node(): HTMLElement {
return this.modalList.first.nativeElement;
}
ngAfterViewInit() {
this._connector.addModal(this);
}
ngOnDestroy() {
this._connector.removeModal(this);
}
}
@Kishore2Learn
Copy link

Hi Ryan,
I found your component as very useful. I am not able to write a component to use them. Please provide example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment