Skip to content

Commit

Permalink
feat(modal): add modal service events, fix modal content onDestroy [f…
Browse files Browse the repository at this point in the history
…ixes #2256] (#2272)
  • Loading branch information
IlyaSurmay authored and valorkin committed Jul 26, 2017
1 parent 8664bb1 commit c9f85e6
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 13 deletions.
4 changes: 3 additions & 1 deletion demo/src/app/components/+modal/demos/events/events.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<button type="button" class="btn btn-primary" (click)="modal.show()">Open a modal</button>
<button type="button" class="btn btn-primary" (click)="showModal()">Open a modal</button>
<br><br>
<pre *ngFor="let message of messages">{{message}}</pre>

<div class="modal fade" bsModal #modal="bs-modal" tabindex="-1" role="dialog"
aria-labelledby="mySmallModalLabel" aria-hidden="true"
Expand Down
11 changes: 9 additions & 2 deletions demo/src/app/components/+modal/demos/events/events.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { Component } from '@angular/core';
import { Component, ViewChild } from '@angular/core';
import { ModalDirective } from 'ngx-bootstrap/modal/modal.component';

@Component({
selector: 'demo-modal-events',
templateUrl: './events.html'
})
export class DemoModalEventsComponent {
@ViewChild(ModalDirective) public modal: ModalDirective;
public messages: string[];

public showModal() {
this.messages = [];
this.modal.show();
}
public handler(type: string, $event: ModalDirective) {
console.log(`event ${type} is fired${$event.dismissReason ? ', dismissed by ' + $event.dismissReason : ''}`);
this.messages.push(`event ${type} is fired${$event.dismissReason ? ', dismissed by ' + $event.dismissReason : ''}`);
}
}
8 changes: 7 additions & 1 deletion demo/src/app/components/+modal/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DemoModalServiceStaticComponent } from './service-template/service-temp
import { DemoModalServiceFromComponent } from './service-component/service-component';
import { DemoModalServiceNestedComponent } from './service-nested/service-nested';
import { DemoModalServiceOptionsComponent } from './service-options/service-options';
import { DemoModalServiceEventsComponent } from './service-events/service-events';

export const DEMO_COMPONENTS = [
DemoModalSizesComponent,
Expand All @@ -19,7 +20,8 @@ export const DEMO_COMPONENTS = [
DemoModalServiceFromComponent,
DemoModalServiceNestedComponent,
DemoModalServiceOptionsComponent,
DemoModalEventsComponent
DemoModalEventsComponent,
DemoModalServiceEventsComponent
];

export const DEMOS = {
Expand Down Expand Up @@ -62,5 +64,9 @@ export const DEMOS = {
serviceOptions: {
component: require('!!raw-loader?lang=typescript!./service-options/service-options.ts'),
html: require('!!raw-loader?lang=markup!./service-options/service-options.html')
},
serviceEvents: {
component: require('!!raw-loader?lang=typescript!./service-events/service-events.ts'),
html: require('!!raw-loader?lang=markup!./service-events/service-events.html')
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<button type="button" class="btn btn-primary" (click)="openModal(template)">Open modal</button>
<br><br>
<pre *ngFor="let message of messages">{{message}}</pre>
<template #template>
<div class="modal-header">
<h4 class="modal-title pull-left">Modal</h4>
<button type="button" class="close pull-right" aria-label="Close" (click)="modalRef.hide()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
This is a modal
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Component, TemplateRef } from '@angular/core';
import { BsModalService } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal/modal-options.class';
import { Subscription } from 'rxjs/Subscription';

@Component({
selector: 'demo-modal-service-events',
templateUrl: './service-events.html'
})
export class DemoModalServiceEventsComponent {
public modalRef: BsModalRef;
public subscriptions: Subscription[] = [];
public messages: string[] = [];
constructor(private modalService: BsModalService) {}

public openModal(template: TemplateRef<any>) {
this.messages = [];
this.subscriptions.push(this.modalService.onShow.subscribe((reason: string) => {
this.messages.push(`onShow event has been fired`);
}));
this.subscriptions.push(this.modalService.onShown.subscribe((reason: string) => {
this.messages.push(`onShown event has been fired`);
}));
this.subscriptions.push(this.modalService.onHide.subscribe((reason: string) => {
this.messages.push(`onHide event has been fired${reason ? ', dismissed by ' + reason : ''}`);
}));
this.subscriptions.push(this.modalService.onHidden.subscribe((reason: string) => {
this.messages.push(`onHidden event has been fired${reason ? ', dismissed by ' + reason : ''}`);
this.unsubscribe();
}));
this.modalRef = this.modalService.show(template);
}


public unsubscribe() {
this.subscriptions.forEach((subscription: Subscription) => {
subscription.unsubscribe();
});
this.subscriptions = [];
}
}
8 changes: 8 additions & 0 deletions demo/src/app/components/+modal/modal-section.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
<li><a routerLink="." fragment="service-template">Template</a></li>
<li><a routerLink="." fragment="service-component">Component</a></li>
<li><a routerLink="." fragment="service-nested">Nested</a></li>
<li><a routerLink="." fragment="service-events">Events</a></li>
<li><a routerLink="." fragment="service-options">Options</a></li>
</ul>
</li>
Expand Down Expand Up @@ -78,6 +79,13 @@ let titleDoc = require('html-loader!markdown-loader!./docs/title.md');
<demo-modal-service-nested></demo-modal-service-nested>
</ng-sample-box>
<h3 routerLink="." fragment="service-events" id="service-events">Events</h3>
<p>Modal service events. Modal service exposes 4 events: onShow, onShown, onHide, onHidden. See usage example below.</p>
<p>onHide and onHidden emit dismiss reason. Possible values are <code>backdrop-click</code>, <code>esc</code> or <code>null</code> if modal was closed by direct call of <code>hide()</code></p>
<ng-sample-box [ts]="demos.serviceEvents.component" [html]="demos.serviceEvents.html">
<demo-modal-service-events></demo-modal-service-events>
</ng-sample-box>
<h3 routerLink="." fragment="service-options" id="service-options">Options</h3>
<p>There are some options that you can configure, like animation, backdrop, closing by Esc button, additional css classes. See the demo below to learn how to configure your modal</p>
<ng-sample-box [ts]="demos.serviceOptions.component" [html]="demos.serviceOptions.html">
Expand Down
3 changes: 3 additions & 0 deletions src/component-loader/component-loader.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ export class ComponentLoader<T> {

const componentEl = this._componentRef.location.nativeElement;
componentEl.parentNode.removeChild(componentEl);
if (this._contentRef.componentRef) {
this._contentRef.componentRef.destroy();
}
this._componentRef.destroy();
if (this._viewContainerRef && this._contentRef.viewRef) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._contentRef.viewRef));
Expand Down
29 changes: 26 additions & 3 deletions src/modal/bs-modal.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentRef, Injectable, TemplateRef } from '@angular/core';
import { ComponentRef, Injectable, TemplateRef, EventEmitter } from '@angular/core';

import { ComponentLoader } from '../component-loader/component-loader.class';
import { ComponentLoaderFactory } from '../component-loader/component-loader.factory';
Expand All @@ -11,14 +11,21 @@ export class BsModalService {
// constructor props
public config: ModalOptions = modalConfigDefaults;

public onShow: EventEmitter<any> = new EventEmitter();
public onShown: EventEmitter<any> = new EventEmitter();
public onHide: EventEmitter<any> = new EventEmitter();
public onHidden: EventEmitter<any> = new EventEmitter();

protected isBodyOverflowing: boolean = false;
protected originalBodyPadding: number = 0;

protected scrollbarWidth: number = 0;

protected backdropRef: ComponentRef<ModalBackdropComponent>;

private _backdropLoader: ComponentLoader<ModalBackdropComponent>;
private modalsCount: number = 0;
private lastDismissReason: string = '';

private loaders: ComponentLoader<ModalContainerComponent>[] = [];

public constructor(private clf: ComponentLoaderFactory) {
Expand All @@ -31,6 +38,7 @@ export class BsModalService {
this._createLoaders();
this.config = Object.assign({}, modalConfigDefaults, config);
this._showBackdrop();
this.lastDismissReason = null;
return this._showModal(content);
}

Expand Down Expand Up @@ -100,6 +108,10 @@ export class BsModalService {
return this.modalsCount;
}

setDismissReason(reason: string) {
this.lastDismissReason = reason;
}

protected removeBackdrop(): void {
this._backdropLoader.hide();
this.backdropRef = null;
Expand Down Expand Up @@ -141,7 +153,12 @@ export class BsModalService {
}

private _createLoaders(): void {
this.loaders.push(this.clf.createLoader<ModalContainerComponent>(null, null, null));
const loader = this.clf.createLoader<ModalContainerComponent>(null, null, null);
this.copyEvent(loader.onBeforeShow, this.onShow);
this.copyEvent(loader.onShown, this.onShown);
this.copyEvent(loader.onBeforeHide, this.onHide);
this.copyEvent(loader.onHidden, this.onHidden);
this.loaders.push(loader);
}

private removeLoaders(level: number): void {
Expand All @@ -150,4 +167,10 @@ export class BsModalService {
loader.instance.level = i + 1;
});
}

private copyEvent(from: EventEmitter<any>, to: EventEmitter<any>) {
from.subscribe(() => {
to.emit(this.lastDismissReason);
});
}
}
4 changes: 3 additions & 1 deletion src/modal/modal-container.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, ElementRef, HostListener, OnDestroy, OnInit, Renderer } from '@angular/core';
import { ClassName, ModalOptions, TransitionDurations } from './modal-options.class';
import { ClassName, DISMISS_REASONS, ModalOptions, TransitionDurations } from './modal-options.class';
import { BsModalService } from './bs-modal.service';
import { isBs3 } from '../utils/ng2-bootstrap-config';

Expand Down Expand Up @@ -30,11 +30,13 @@ export class ModalContainerComponent implements OnInit, OnDestroy {
if (this.config.ignoreBackdropClick || this.config.backdrop === 'static' || event.target !== this._element.nativeElement) {
return;
}
this.bsModalService.setDismissReason(DISMISS_REASONS.BACKRDOP);
this.hide();
}
@HostListener('window:keydown.esc')
public onEsc(): void {
if (this.config.keyboard && this.level === this.bsModalService.getModalsCount()) {
this.bsModalService.setDismissReason(DISMISS_REASONS.ESC);
this.hide();
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/modal/modal-options.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,8 @@ export const TransitionDurations: any = {
MODAL: 300,
BACKDROP: 150
};

export const DISMISS_REASONS = {
BACKRDOP: 'backdrop-click',
ESC: 'esc'
};
6 changes: 1 addition & 5 deletions src/modal/modal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,14 @@ import { document } from '../utils/facade/browser';
import { isBs3 } from '../utils/ng2-bootstrap-config';
import { Utils } from '../utils/utils.class';
import { ModalBackdropComponent } from './modal-backdrop.component';
import { ClassName, modalConfigDefaults, ModalOptions, Selector } from './modal-options.class';
import { ClassName, modalConfigDefaults, ModalOptions, Selector, DISMISS_REASONS } from './modal-options.class';

import { window } from '../utils/facade/browser';
import { ComponentLoader } from '../component-loader/component-loader.class';
import { ComponentLoaderFactory } from '../component-loader/component-loader.factory';

const TRANSITION_DURATION = 300;
const BACKDROP_TRANSITION_DURATION = 150;
const DISMISS_REASONS = {
BACKRDOP: 'backdrop-click',
ESC: 'esc'
};

/** Mark any code with directive to show it's content in modal */
@Directive({
Expand Down

0 comments on commit c9f85e6

Please sign in to comment.