Skip to content
This repository has been archived by the owner on Sep 7, 2020. It is now read-only.

Commit

Permalink
Fix onDismiss not emitting correctly. Closes issue #27.
Browse files Browse the repository at this point in the history
  • Loading branch information
Douglas Ludlow committed Apr 2, 2016
1 parent 92e2d77 commit 3b36adb
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 68 deletions.
2 changes: 1 addition & 1 deletion demo/modal-demo.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ <h1>Modal</h1>
<button type="button" class="btn btn-default" (click)="animationsEnabled = !animationsEnabled">Toggle Animation ({{ animationsEnabled }})</button>
<button type="button" class="btn btn-default" (click)="open()">Open from component</button>
</p>
<p [hidden]="!selected">Selection from a modal: {{ selected }}</p>
<p [hidden]="!selected">Selection from modal: {{ selected }}</p>

<modal [animation]="animationsEnabled" (onClose)="closed()" (onDismiss)="dismissed()" #modal>
<modal-header [show-close]="true">
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ng2-bs3-modal",
"version": "0.4.5",
"version": "0.4.6",
"description": "Angular2 Boostrap3 Modal Component",
"main": "ng2-bs3-modal.js",
"scripts": {
Expand Down
79 changes: 33 additions & 46 deletions src/ng2-bs3-modal/components/modal-instance.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import { ElementRef } from 'angular2/core';
import { Observable } from 'rxjs/Observable';
import { ConnectableObservable } from 'rxjs/observable/ConnectableObservable';
import { Observer } from 'rxjs/Observer';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/publish';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/fromEvent';

declare var jQuery: any;

export class ModalInstance {

private suffix: string = 'ng2-bs3-modal';
private shownEventName: string = 'shown.bs.modal';
private hiddenEventName: string = 'hidden.bs.modal';
private suffix: string = '.ng2-bs3-modal';
private shownEventName: string = 'shown.bs.modal' + this.suffix;
private hiddenEventName: string = 'hidden.bs.modal' + this.suffix;

$modal: any;
shown: ConnectableObservable<any>;
shownObserver: Observer<any>;
hidden: ConnectableObservable<ModalResult>;
hiddenObserver: Observer<ModalResult>;
shown: Observable<void>;
hidden: Observable<ModalResult>;
result: ModalResult;
visible: boolean = false;

Expand All @@ -27,7 +22,6 @@ export class ModalInstance {
}

open(): Promise<any> {
this.create();
return this.show();
}

Expand All @@ -51,57 +45,50 @@ export class ModalInstance {
}

private show() {
this.$modal.appendTo('body');
let promise = toPromise(this.shown);
this.$modal.modal('show');
return this.shown.toPromise();
return promise;
}

private hide(): Promise<ModalResult> {
if (this.$modal) {
if (this.$modal && this.visible) {
let promise = toPromise(this.hidden);
this.$modal.modal('hide');
return this.hidden.toPromise();
return promise;
}
return Promise.resolve(this.result);
}

private init() {
this.shown = new Observable<any>(observer => {
this.shownObserver = observer;
}).publish();
this.$modal = jQuery(this.element.nativeElement.firstElementChild);
this.$modal.appendTo('body').modal({ show: false });

this.hidden = new Observable<ModalResult>(observer => {
this.hiddenObserver = observer;
}).publish();

this.hidden.connect();
this.shown.connect();
}
this.shown = Observable.fromEvent(this.$modal, this.shownEventName)
.map(() => {
this.visible = true;
});

private create() {
if (!this.$modal) {
this.$modal = jQuery(this.element.nativeElement.firstElementChild);
this.$modal.appendTo('body').modal({ show: false });
}
this.hidden = Observable.fromEvent(this.$modal, this.hiddenEventName)
.map(() => {
let result = (!this.result || this.result === ModalResult.None)
? ModalResult.Dismiss : this.result;

this.$modal
.off(`${this.shownEventName}.${this.suffix}`)
.on(`${this.shownEventName}.${this.suffix}`, () => {
this.visible = true;
this.shownObserver.next(undefined);
this.shownObserver.complete();
})
.off(`${this.hiddenEventName}.${this.suffix}`)
.on(`${this.hiddenEventName}.${this.suffix}`, () => {
this.result = ModalResult.None;
this.visible = false;
if (this.result === ModalResult.None) {
this.result = ModalResult.Dismiss;
}
this.hiddenObserver.next(this.result);
this.hiddenObserver.complete();

return result;
});
}
}

function toPromise<T>(observable: Observable<T>): Promise<T> {
return new Promise((resolve, reject) => {
observable.subscribe(next => {
resolve(next);
});
});
}

export enum ModalResult {
None,
Close,
Expand Down
20 changes: 11 additions & 9 deletions src/ng2-bs3-modal/components/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ModalInstance, ModalResult } from './modal-instance';
</div>
`
})
export class ModalComponent implements OnDestroy, CanDeactivate {
export class ModalComponent implements AfterViewInit, OnDestroy, CanDeactivate {

instance: ModalInstance;
overrideSize: string = null;
Expand All @@ -27,20 +27,24 @@ export class ModalComponent implements OnDestroy, CanDeactivate {
@Output() onClose: EventEmitter<any> = new EventEmitter(false);
@Output() onDismiss: EventEmitter<any> = new EventEmitter(false);

constructor(el: ElementRef) {
this.instance = new ModalInstance(el);
constructor(private element: ElementRef) {
}

ngAfterViewInit() {
this.instance = new ModalInstance(this.element);
this.instance.hidden.subscribe((result) => {
if (result = ModalResult.Dismiss)
this.visible = this.instance.visible;
if (result === ModalResult.Dismiss)
this.onDismiss.emit(undefined);
});
}

ngOnDestroy() {
this.instance.destroy();
return this.instance && this.instance.destroy();
}

routerCanDeactivate(next: ComponentInstruction, prev: ComponentInstruction): any {
return this.instance.destroy();
return this.ngOnDestroy();
}

open(size?: string): Promise<any> {
Expand All @@ -52,15 +56,13 @@ export class ModalComponent implements OnDestroy, CanDeactivate {

close(): Promise<any> {
return this.instance.close().then(() => {
this.visible = this.instance.visible;
this.onClose.emit(undefined);
});
}

dismiss(): Promise<any> {
return this.instance.dismiss().then(() => {
this.visible = this.instance.visible;
this.onDismiss.emit(undefined);
// this.onDismiss.emit(undefined);
});
}

Expand Down
93 changes: 82 additions & 11 deletions test/ng2-bs3-modal.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
beforeEach,
beforeEachProviders,
afterEach,
describe,
expect,
it,
Expand Down Expand Up @@ -43,21 +44,31 @@ describe('ModalComponent', () => {
router = r;
}));

afterEach(() => {
fixture && fixture.destroy();
});

it('should render',
injectAsync([TestComponentBuilder], (builder: TestComponentBuilder) => {
return builder.createAsync(TestComponent).then((fixture: ComponentFixture) => {
const element = fixture.nativeElement;
expect(element.querySelectorAll('.modal').length).toBe(1);
return builder.createAsync(TestComponent).then(f => {
fixture = f;
fixture.detectChanges();
expect(document.querySelectorAll('.modal').length).toBe(1);
});
}));

it('should cleanup when destroyed',
injectAsync([TestComponentBuilder], (builder: TestComponentBuilder) => {
return builder.createAsync(TestComponent).then((fixture: ComponentFixture) => {
fixture.destroy();
it('should cleanup when destroyed', done => {
builder.createAsync(TestComponent).then(f => {
fixture = f;
testComponent = fixture.componentInstance;
fixture.detectChanges();
testComponent.modal.ngOnDestroy();
setTimeout(() => {
expect(document.querySelectorAll('.modal').length).toBe(0);
});
}));
done();
}, 1000);
});
});

it('should emit onClose when modal is closed', done => {
builder.createAsync(TestComponent)
Expand Down Expand Up @@ -102,6 +113,27 @@ describe('ModalComponent', () => {
.then(() => testComponent.modal.dismiss());
});

it('should emit onDismiss only once', done => {
let times = 0;

setTimeout(() => {
expect(times).toBe(1);
done();
}, 1000);

builder.createAsync(TestComponent)
.then(f => { fixture = f; })
.then(() => { testComponent = fixture.componentInstance; })
.then(() => {
fixture.detectChanges();
testComponent.modal.onDismiss.subscribe(() => {
times++;
});
})
.then(() => testComponent.modal.open())
.then(() => testComponent.modal.dismiss());
});

it('should emit onDismiss when modal is dimissed and animation is disabled', done => {
builder.createAsync(TestComponent)
.then(f => { fixture = f; })
Expand All @@ -117,7 +149,46 @@ describe('ModalComponent', () => {
.then(() => testComponent.modal.dismiss());
});

it('should not throw an error when navigating in modal dismiss/close', (done) => {
it('should emit onDismiss when modal is dimissed a second time from backdrop', done => {
let times = 0;
builder.createAsync(TestComponent)
.then(f => { fixture = f; })
.then(() => { testComponent = fixture.componentInstance; })
.then(() => {
fixture.detectChanges();
testComponent.modal.onDismiss.subscribe(() => {
times++;
if (times === 2) done();
});
})
.then(() => testComponent.modal.open())
.then(() => testComponent.modal.dismiss())
.then(() => testComponent.modal.open())
.then(() => {
document.querySelector('.modal.in').click();
});
});

it('should emit onDismiss when modal is closed, opened, then dimissed from backdrop', done => {
let times = 0;
builder.createAsync(TestComponent)
.then(f => { fixture = f; })
.then(() => { testComponent = fixture.componentInstance; })
.then(() => {
fixture.detectChanges();
testComponent.modal.onDismiss.subscribe(() => {
done();
});
})
.then(() => testComponent.modal.open())
.then(() => testComponent.modal.close())
.then(() => testComponent.modal.open())
.then(() => {
document.querySelector('.modal.in').click();
});
});

it('should not throw an error when navigating on modal close', done => {
builder.createAsync(TestAppComponent)
.then(f => { fixture = f; })
.then(() => router.navigateByUrl('/test1'))
Expand All @@ -134,7 +205,7 @@ describe('ModalComponent', () => {
});
})
.then(() => testComponent.modal.open())
.then(() => testComponent.modal.close())
.then(() => testComponent.modal.close());
});
});

Expand Down

0 comments on commit 3b36adb

Please sign in to comment.