Skip to content

Instantly share code, notes, and snippets.

@ValentinFunk
Last active October 9, 2018 19:26
Show Gist options
  • Save ValentinFunk/5f1aafae2af46d42d2804ad03411862f to your computer and use it in GitHub Desktop.
Save ValentinFunk/5f1aafae2af46d42d2804ad03411862f to your computer and use it in GitHub Desktop.
@ngrx/store and @ng-bootstrap/ng-bootstrap NgbModal
import { Injectable, Component, ɵisObservable as isObservable, Type } from '@angular/core';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { Store, Action } from '@ngrx/store';
import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
type SelectFn = (appState: AppState) => boolean;
type ActionCreatorFn = (reason: string) => Action;
/**
* Keep Modal in Sync with Application State.
* (A bit hacky because ngbootstrap modal wasn't built for ngrx/Store)
*/
@Injectable()
export class ModalService {
private modals: {
[index: string]: NgbModalRef
} = {};
constructor(
private modalService: NgbModal,
private store: Store<any>
) {}
/**
* Add a modal listener.
*
* @param id Unique id for this component.
* @param component Component type to instantiate.
* @param selectFn Selector selecting a boolean state attribute that determines modal visibility.
* @param closeAction Action creator, this action is fired if the modal is closed.
*/
addModalListener(
id: string,
component: Type<any>,
selectFn: SelectFn,
closeAction: ActionCreatorFn): Subscription;
/**
* Add a modal listener for a component of a lazy-loaded module.
*
* @param id Unique id for this component.
* @param component Component type to instantiate.
* @param selectFn Selector selecting a boolean state attribute that determines modal visibility.
* @param closeAction Action creator, this action is fired if the modal is closed.
* @param lazyModalService NgbModal instance of the lazy loaded module.
*/
addModalListener(
id: string,
component: Type<any>,
selectFn: SelectFn,
closeAction: ActionCreatorFn,
lazyModalService: NgbModal): Subscription;
addModalListener(
id: string,
component: Type<any>,
selectFn: SelectFn,
closeAction: ActionCreatorFn,
lazyModalService?: NgbModal,
): Subscription {
lazyModalService = lazyModalService || this.modalService;
return this.addModalListenerEx(id, component, selectFn, closeAction, undefined, lazyModalService);
}
/**
* Add a modal listener with the ability to pass in custom options.
*
* @param id Unique id for this component.
* @param component Component type to instantiate.
* @param selectFn Selector selecting a boolean state attribute that determines modal visibility.
* @param closeAction Action creator, this action is fired if the modal is closed.
* @param options NgbModalOptions to use. If an observable is passed the latest emitted value is used before each modal open.
* @param lazyModalService NgbModal instance of the lazy loaded module.
*/
addModalListenerEx(
id: string,
component: Type<any>,
selectFn: (state: any) => boolean,
closeAction: (reason: string) => Action,
options: {} | Observable<{}>,
lazyModalService: NgbModal,
): Subscription {
lazyModalService = lazyModalService || this.modalService;
const optionsObservable: Observable<{}> = (options && isObservable(options)) ? options : Observable.of(options);
const subscription = this.store.select(selectFn)
.withLatestFrom(optionsObservable)
.subscribe(([showModal, resolvedOptions]) => {
if (showModal) {
if (!this.modals[id] || !this.modals[id].componentInstance) {
this.modals[id] = lazyModalService.open(component, resolvedOptions);
// If modal is closed by clicking outside of it we still need to set the correct state.
this.modals[id].result.then(() => {
this.modals[id] = null;
this.store.dispatch(closeAction('External'));
}, () => {
this.modals[id] = null;
this.store.dispatch(closeAction('External'));
});
}
} else {
if (this.modals[id] && this.modals[id].componentInstance) {
this.modals[id].close();
this.modals[id] = null;
}
}
});
return subscription;
}
}
@NgModule({...})
export class UserModule {
constructor(
ngbModal: NgbModal,
modalService: ModalService,
store: Store<AppState>
) {
// Disallow closing the login modal if user is accessing a protected route as first page.
// (else they would get an empty page due to the auth guard)
let loginModalOptions = store.select(x => x.user.loginRequired)
.map(required => required ? {
backdrop: 'static',
keyboard: false
} : {});
modalService.addModalListenerEx(
'Login',
LoginModalComponent,
appState => appState.user.loginModalVisible,
() => new userActions.CloseLoginModalAction(),
loginModalOptions,
ngbModal
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment