Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(modal): add modal service events, fix modal content onDestroy [fixes #2256] #2272

Merged
merged 1 commit into from
Jul 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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