mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
feature(modal): support lazy loading of modal pages
This commit is contained in:
@ -5,6 +5,7 @@ import { NavParams } from '../../navigation/nav-params';
|
|||||||
import { NavOptions } from '../../navigation/nav-util';
|
import { NavOptions } from '../../navigation/nav-util';
|
||||||
import { ViewController } from '../../navigation/view-controller';
|
import { ViewController } from '../../navigation/view-controller';
|
||||||
import { GestureController, BlockerDelegate, GESTURE_MENU_SWIPE, GESTURE_GO_BACK_SWIPE } from '../../gestures/gesture-controller';
|
import { GestureController, BlockerDelegate, GESTURE_MENU_SWIPE, GESTURE_GO_BACK_SWIPE } from '../../gestures/gesture-controller';
|
||||||
|
import { ModuleLoader } from '../../util/module-loader';
|
||||||
import { assert } from '../../util/util';
|
import { assert } from '../../util/util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,7 +32,9 @@ export class ModalCmp {
|
|||||||
public _renderer: Renderer,
|
public _renderer: Renderer,
|
||||||
public _navParams: NavParams,
|
public _navParams: NavParams,
|
||||||
public _viewCtrl: ViewController,
|
public _viewCtrl: ViewController,
|
||||||
gestureCtrl: GestureController
|
gestureCtrl: GestureController,
|
||||||
|
public moduleLoader: ModuleLoader
|
||||||
|
|
||||||
) {
|
) {
|
||||||
let opts = _navParams.get('opts');
|
let opts = _navParams.get('opts');
|
||||||
assert(opts, 'modal data must be valid');
|
assert(opts, 'modal data must be valid');
|
||||||
@ -49,7 +52,12 @@ export class ModalCmp {
|
|||||||
/** @private */
|
/** @private */
|
||||||
_load(component: any) {
|
_load(component: any) {
|
||||||
if (component) {
|
if (component) {
|
||||||
const componentFactory = this._cfr.resolveComponentFactory(component);
|
|
||||||
|
let cfr = this.moduleLoader.getComponentFactoryResolver(component);
|
||||||
|
if (!cfr) {
|
||||||
|
cfr = this._cfr;
|
||||||
|
}
|
||||||
|
const componentFactory = cfr.resolveComponentFactory(component);
|
||||||
|
|
||||||
// ******** DOM WRITE ****************
|
// ******** DOM WRITE ****************
|
||||||
const componentRef = this._viewport.createComponent(componentFactory, this._viewport.length, this._viewport.parentInjector, []);
|
const componentRef = this._viewport.createComponent(componentFactory, this._viewport.length, this._viewport.parentInjector, []);
|
||||||
|
@ -4,6 +4,7 @@ import { App } from '../app/app';
|
|||||||
import { Config } from '../../config/config';
|
import { Config } from '../../config/config';
|
||||||
import { Modal } from './modal';
|
import { Modal } from './modal';
|
||||||
import { ModalOptions } from './modal-options';
|
import { ModalOptions } from './modal-options';
|
||||||
|
import { DeepLinker } from '../../navigation/deep-linker';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name ModalController
|
* @name ModalController
|
||||||
@ -116,7 +117,7 @@ import { ModalOptions } from './modal-options';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ModalController {
|
export class ModalController {
|
||||||
|
|
||||||
constructor(private _app: App, public config: Config) { }
|
constructor(private _app: App, public config: Config, private deepLinker: DeepLinker) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a modal to display. See below for options.
|
* Create a modal to display. See below for options.
|
||||||
@ -126,6 +127,6 @@ export class ModalController {
|
|||||||
* @param {object} opts Modal options
|
* @param {object} opts Modal options
|
||||||
*/
|
*/
|
||||||
create(component: any, data: any = {}, opts: ModalOptions = {}) {
|
create(component: any, data: any = {}, opts: ModalOptions = {}) {
|
||||||
return new Modal(this._app, component, data, opts, this.config);
|
return new Modal(this._app, component, data, opts, this.config, this.deepLinker);
|
||||||
}
|
}
|
||||||
}
|
}
|
69
src/components/modal/modal-impl.ts
Normal file
69
src/components/modal/modal-impl.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { App } from '../app/app';
|
||||||
|
import { Config } from '../../config/config';
|
||||||
|
import { isPresent } from '../../util/util';
|
||||||
|
import { PORTAL_MODAL } from '../app/app-constants';
|
||||||
|
import { ModalCmp } from './modal-component';
|
||||||
|
import { ModalOptions } from './modal-options';
|
||||||
|
import { ModalSlideIn, ModalSlideOut, ModalMDSlideIn, ModalMDSlideOut } from './modal-transitions';
|
||||||
|
import { NavOptions } from '../../navigation/nav-util';
|
||||||
|
import { ViewController } from '../../navigation/view-controller';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export class ModalImpl extends ViewController {
|
||||||
|
private _app: App;
|
||||||
|
private _enterAnimation: string;
|
||||||
|
private _leaveAnimation: string;
|
||||||
|
|
||||||
|
constructor(app: App, component: any, data: any, opts: ModalOptions = {}, config: Config) {
|
||||||
|
data = data || {};
|
||||||
|
data.component = component;
|
||||||
|
opts.showBackdrop = isPresent(opts.showBackdrop) ? !!opts.showBackdrop : true;
|
||||||
|
opts.enableBackdropDismiss = isPresent(opts.enableBackdropDismiss) ? !!opts.enableBackdropDismiss : true;
|
||||||
|
data.opts = opts;
|
||||||
|
|
||||||
|
super(ModalCmp, data, null);
|
||||||
|
this._app = app;
|
||||||
|
this._enterAnimation = opts.enterAnimation;
|
||||||
|
this._leaveAnimation = opts.leaveAnimation;
|
||||||
|
|
||||||
|
this.isOverlay = true;
|
||||||
|
|
||||||
|
config.setTransition('modal-slide-in', ModalSlideIn);
|
||||||
|
config.setTransition('modal-slide-out', ModalSlideOut);
|
||||||
|
config.setTransition('modal-md-slide-in', ModalMDSlideIn);
|
||||||
|
config.setTransition('modal-md-slide-out', ModalMDSlideOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
getTransitionName(direction: string): string {
|
||||||
|
let key: string;
|
||||||
|
if (direction === 'back') {
|
||||||
|
if (this._leaveAnimation) {
|
||||||
|
return this._leaveAnimation;
|
||||||
|
}
|
||||||
|
key = 'modalLeave';
|
||||||
|
} else {
|
||||||
|
if (this._enterAnimation) {
|
||||||
|
return this._enterAnimation;
|
||||||
|
}
|
||||||
|
key = 'modalEnter';
|
||||||
|
}
|
||||||
|
return this._nav && this._nav.config.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Present the action sheet instance.
|
||||||
|
*
|
||||||
|
* @param {NavOptions} [opts={}] Nav options to go with this transition.
|
||||||
|
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
|
||||||
|
*/
|
||||||
|
present(navOptions: NavOptions = {}) {
|
||||||
|
navOptions.minClickBlockDuration = navOptions.minClickBlockDuration || 400;
|
||||||
|
return this._app.present(this, navOptions, PORTAL_MODAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,70 +1,25 @@
|
|||||||
import { App } from '../app/app';
|
import { App } from '../app/app';
|
||||||
import { Config } from '../../config/config';
|
import { Config } from '../../config/config';
|
||||||
import { isPresent } from '../../util/util';
|
|
||||||
import { PORTAL_MODAL } from '../app/app-constants';
|
|
||||||
import { ModalCmp } from './modal-component';
|
|
||||||
import { ModalOptions } from './modal-options';
|
|
||||||
import { ModalSlideIn, ModalSlideOut, ModalMDSlideIn, ModalMDSlideOut } from './modal-transitions';
|
|
||||||
import { NavOptions } from '../../navigation/nav-util';
|
|
||||||
import { ViewController } from '../../navigation/view-controller';
|
|
||||||
|
|
||||||
|
import { ModalOptions } from './modal-options';
|
||||||
|
import { DeepLinker } from '../../navigation/deep-linker';
|
||||||
|
|
||||||
|
import { Overlay } from '../../navigation/overlay';
|
||||||
|
import { OverlayProxy } from '../../navigation/overlay-proxy';
|
||||||
|
import { ModalImpl } from './modal-impl';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export class Modal extends ViewController {
|
export class Modal extends OverlayProxy {
|
||||||
private _app: App;
|
|
||||||
private _enterAnimation: string;
|
|
||||||
private _leaveAnimation: string;
|
|
||||||
|
|
||||||
constructor(app: App, component: any, data: any, opts: ModalOptions = {}, config: Config) {
|
public isOverlay: boolean = true;
|
||||||
data = data || {};
|
|
||||||
data.component = component;
|
|
||||||
opts.showBackdrop = isPresent(opts.showBackdrop) ? !!opts.showBackdrop : true;
|
|
||||||
opts.enableBackdropDismiss = isPresent(opts.enableBackdropDismiss) ? !!opts.enableBackdropDismiss : true;
|
|
||||||
data.opts = opts;
|
|
||||||
|
|
||||||
super(ModalCmp, data, null);
|
constructor(app: App, component: any, private data: any, private opts: ModalOptions = {}, config: Config, deepLinker: DeepLinker) {
|
||||||
this._app = app;
|
super(app, component, config, deepLinker);
|
||||||
this._enterAnimation = opts.enterAnimation;
|
|
||||||
this._leaveAnimation = opts.leaveAnimation;
|
|
||||||
|
|
||||||
this.isOverlay = true;
|
|
||||||
|
|
||||||
config.setTransition('modal-slide-in', ModalSlideIn);
|
|
||||||
config.setTransition('modal-slide-out', ModalSlideOut);
|
|
||||||
config.setTransition('modal-md-slide-in', ModalMDSlideIn);
|
|
||||||
config.setTransition('modal-md-slide-out', ModalMDSlideOut);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getImplementation(): Overlay {
|
||||||
* @private
|
return new ModalImpl(this._app, this._component, this.data, this.opts, this._config);
|
||||||
*/
|
|
||||||
getTransitionName(direction: string): string {
|
|
||||||
let key: string;
|
|
||||||
if (direction === 'back') {
|
|
||||||
if (this._leaveAnimation) {
|
|
||||||
return this._leaveAnimation;
|
|
||||||
}
|
|
||||||
key = 'modalLeave';
|
|
||||||
} else {
|
|
||||||
if (this._enterAnimation) {
|
|
||||||
return this._enterAnimation;
|
|
||||||
}
|
|
||||||
key = 'modalEnter';
|
|
||||||
}
|
|
||||||
return this._nav && this._nav.config.get(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Present the action sheet instance.
|
|
||||||
*
|
|
||||||
* @param {NavOptions} [opts={}] Nav options to go with this transition.
|
|
||||||
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
|
|
||||||
*/
|
|
||||||
present(navOptions: NavOptions = {}) {
|
|
||||||
navOptions.minClickBlockDuration = navOptions.minClickBlockDuration || 400;
|
|
||||||
return this._app.present(this, navOptions, PORTAL_MODAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
import { mockApp, mockConfig } from '../../../util/mock-providers';
|
import { mockApp, mockConfig, mockDeepLinker } from '../../../util/mock-providers';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ModalController } from '../modal-controller';
|
import { ModalController } from '../modal-controller';
|
||||||
import { ModalCmp } from '../modal-component';
|
import { ModalCmp } from '../modal-component';
|
||||||
import { ViewController } from '../../../navigation/view-controller';
|
|
||||||
|
|
||||||
describe('Modal', () => {
|
describe('Modal', () => {
|
||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
|
|
||||||
it('should have the correct properties on modal view controller instance', () => {
|
it('should have the correct properties on modal view controller proxy instance', () => {
|
||||||
let modalCtrl = new ModalController(mockApp(), mockConfig());
|
let modalCtrl = new ModalController(mockApp(), mockConfig(), mockDeepLinker());
|
||||||
let modalViewController = modalCtrl.create(ComponentToPresent);
|
let modalViewControllerProxy = modalCtrl.create(ComponentToPresent);
|
||||||
expect(modalViewController.component).toEqual(ModalCmp);
|
expect(modalViewControllerProxy._component).toEqual(ModalCmp);
|
||||||
expect(modalViewController.isOverlay).toEqual(true);
|
|
||||||
expect(modalViewController instanceof ViewController).toEqual(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -125,8 +125,6 @@ export class DeepLinker {
|
|||||||
_history: string[] = [];
|
_history: string[] = [];
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_indexAliasUrl: string;
|
_indexAliasUrl: string;
|
||||||
/** @internal */
|
|
||||||
_cfrMap = new Map<any, ComponentFactoryResolver>();
|
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -277,12 +275,9 @@ export class DeepLinker {
|
|||||||
if (link.loadChildren) {
|
if (link.loadChildren) {
|
||||||
// awesome, looks like we'll lazy load this component
|
// awesome, looks like we'll lazy load this component
|
||||||
// using loadChildren as the URL to request
|
// using loadChildren as the URL to request
|
||||||
return this._moduleLoader.load(link.loadChildren).then(loadedModule => {
|
return this._moduleLoader.load(link.loadChildren).then((response) => {
|
||||||
// kerpow!! we just lazy loaded a component!!
|
link.component = response.component;
|
||||||
// update the existing link with the loaded component
|
return response.component;
|
||||||
link.component = loadedModule.component;
|
|
||||||
this._cfrMap.set(link.component, loadedModule.componentFactoryResolver);
|
|
||||||
return link.component;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,7 +289,8 @@ export class DeepLinker {
|
|||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
resolveComponent(component: any): ComponentFactory<any> {
|
resolveComponent(component: any): ComponentFactory<any> {
|
||||||
let cfr = this._cfrMap.get(component);
|
|
||||||
|
let cfr = this._moduleLoader.getComponentFactoryResolver(component);
|
||||||
if (!cfr) {
|
if (!cfr) {
|
||||||
cfr = this._baseCfr;
|
cfr = this._baseCfr;
|
||||||
}
|
}
|
||||||
@ -599,4 +595,4 @@ export function normalizeUrl(browserUrl: string): string {
|
|||||||
browserUrl = browserUrl.substr(0, browserUrl.length - 1);
|
browserUrl = browserUrl.substr(0, browserUrl.length - 1);
|
||||||
}
|
}
|
||||||
return browserUrl;
|
return browserUrl;
|
||||||
}
|
}
|
77
src/navigation/overlay-proxy.ts
Normal file
77
src/navigation/overlay-proxy.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { App } from '../components/app/app';
|
||||||
|
import { Config } from '../config/config';
|
||||||
|
import { isString } from '../util/util';
|
||||||
|
|
||||||
|
|
||||||
|
import { DeepLinker } from './deep-linker';
|
||||||
|
import { NavOptions } from './nav-util';
|
||||||
|
import { Overlay } from './overlay';
|
||||||
|
|
||||||
|
|
||||||
|
export class OverlayProxy {
|
||||||
|
|
||||||
|
overlay: Overlay;
|
||||||
|
_onWillDismiss: Function;
|
||||||
|
_onDidDismiss: Function;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(public _app: App, public _component: any, public _config: Config, public _deepLinker: DeepLinker) {
|
||||||
|
}
|
||||||
|
|
||||||
|
getImplementation(): Overlay {
|
||||||
|
throw new Error('Child class must implement "getImplementation" method');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Present the modal instance.
|
||||||
|
*
|
||||||
|
* @param {NavOptions} [opts={}] Nav options to go with this transition.
|
||||||
|
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
|
||||||
|
*/
|
||||||
|
present(navOptions: NavOptions = {}) {
|
||||||
|
// check if it's a lazy loaded component, or not
|
||||||
|
const isLazyLoaded = isString(this._component);
|
||||||
|
if (isLazyLoaded) {
|
||||||
|
|
||||||
|
return this._deepLinker.getComponentFromName(this._component).then((loadedComponent: any) => {
|
||||||
|
this._component = loadedComponent;
|
||||||
|
return this.createAndPresentOverlay(navOptions);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return this.createAndPresentOverlay(navOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss(data?: any, role?: any, navOptions?: NavOptions): Promise<any> {
|
||||||
|
if (this.overlay) {
|
||||||
|
return this.overlay.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the current viewController has be successfully dismissed
|
||||||
|
*/
|
||||||
|
onDidDismiss(callback: Function) {
|
||||||
|
this._onDidDismiss = callback;
|
||||||
|
if (this.overlay) {
|
||||||
|
this.overlay.onDidDismiss(this._onDidDismiss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createAndPresentOverlay(navOptions: NavOptions) {
|
||||||
|
this.overlay = this.getImplementation();
|
||||||
|
this.overlay.onWillDismiss(this._onWillDismiss);
|
||||||
|
this.overlay.onDidDismiss(this._onDidDismiss);
|
||||||
|
return this.overlay.present(navOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the current viewController will be dismissed
|
||||||
|
*/
|
||||||
|
onWillDismiss(callback: Function) {
|
||||||
|
this._onWillDismiss = callback;
|
||||||
|
if (this.overlay) {
|
||||||
|
this.overlay.onWillDismiss(this._onWillDismiss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/navigation/overlay.ts
Normal file
8
src/navigation/overlay.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { NavOptions } from './nav-util';
|
||||||
|
|
||||||
|
export interface Overlay {
|
||||||
|
present(opts?: NavOptions): Promise<any>;
|
||||||
|
dismiss(data?: any, role?: any, navOptions?: NavOptions): Promise<any>;
|
||||||
|
onDidDismiss(callback: Function): void;
|
||||||
|
onWillDismiss(callback: Function): void;
|
||||||
|
}
|
119
src/navigation/test/overlay-proxy.spec.ts
Normal file
119
src/navigation/test/overlay-proxy.spec.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import { OverlayProxy } from '../overlay-proxy';
|
||||||
|
|
||||||
|
import { mockApp, mockConfig, mockDeepLinker, mockOverlay } from '../../util/mock-providers';
|
||||||
|
|
||||||
|
describe('Overlay Proxy', () => {
|
||||||
|
describe('dismiss', () => {
|
||||||
|
it('should call dismiss if overlay is loaded', (done: Function) => {
|
||||||
|
|
||||||
|
const instance = new OverlayProxy(mockApp(), 'my-component', mockConfig(), mockDeepLinker());
|
||||||
|
instance.overlay = mockOverlay();
|
||||||
|
spyOn(instance.overlay, instance.overlay.dismiss.name).and.returnValue(Promise.resolve());
|
||||||
|
|
||||||
|
const promise = instance.dismiss(null, null, null);
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
expect(instance.overlay.dismiss).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
}).catch((err: Error) => {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onWillDismiss', () => {
|
||||||
|
it('should update the handler on the overlay object', () => {
|
||||||
|
const instance = new OverlayProxy(mockApp(), 'my-component', mockConfig(), mockDeepLinker());
|
||||||
|
instance.overlay = mockOverlay();
|
||||||
|
spyOn(instance.overlay, instance.overlay.onWillDismiss.name);
|
||||||
|
|
||||||
|
const handler = () => { };
|
||||||
|
instance.onWillDismiss(handler);
|
||||||
|
|
||||||
|
expect(instance.overlay.onWillDismiss).toHaveBeenCalledWith(handler);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onDidDismiss', () => {
|
||||||
|
it('should update the handler on the overlay object', () => {
|
||||||
|
const instance = new OverlayProxy(mockApp(), 'my-component', mockConfig(), mockDeepLinker());
|
||||||
|
instance.overlay = mockOverlay();
|
||||||
|
spyOn(instance.overlay, instance.overlay.onDidDismiss.name);
|
||||||
|
|
||||||
|
const handler = () => { };
|
||||||
|
instance.onDidDismiss(handler);
|
||||||
|
|
||||||
|
expect(instance.overlay.onDidDismiss).toHaveBeenCalledWith(handler);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createAndPresentOverlay', () => {
|
||||||
|
it('should set onWillDismiss and onDidDismiss handlers', (done: Function) => {
|
||||||
|
const instance = new OverlayProxy(mockApp(), 'my-component', mockConfig(), mockDeepLinker());
|
||||||
|
const handler = () => { };
|
||||||
|
instance.onWillDismiss(handler);
|
||||||
|
instance.onDidDismiss(handler);
|
||||||
|
const knownOptions = {};
|
||||||
|
const knownOverlay = mockOverlay();
|
||||||
|
|
||||||
|
spyOn(knownOverlay, knownOverlay.present.name).and.returnValue(Promise.resolve());
|
||||||
|
spyOn(knownOverlay, knownOverlay.onDidDismiss.name);
|
||||||
|
spyOn(knownOverlay, knownOverlay.onWillDismiss.name);
|
||||||
|
spyOn(instance, 'getImplementation').and.returnValue(knownOverlay);
|
||||||
|
|
||||||
|
const promise = instance.createAndPresentOverlay(knownOptions);
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
expect(knownOverlay.present).toHaveBeenCalledWith(knownOptions);
|
||||||
|
expect(knownOverlay.onDidDismiss).toHaveBeenCalledWith(handler);
|
||||||
|
expect(knownOverlay.onWillDismiss).toHaveBeenCalledWith(handler);
|
||||||
|
done();
|
||||||
|
}).catch((err: Error) => {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('present', () => {
|
||||||
|
it('should use present the overlay immediately if the component is not a string', (done: Function) => {
|
||||||
|
const knownComponent = { };
|
||||||
|
const deepLinker = mockDeepLinker();
|
||||||
|
const knownOverlay = mockOverlay();
|
||||||
|
const instance = new OverlayProxy(mockApp(), knownComponent, mockConfig(), deepLinker);
|
||||||
|
const knownOptions = {};
|
||||||
|
|
||||||
|
spyOn(instance, 'getImplementation').and.returnValue(knownOverlay);
|
||||||
|
spyOn(deepLinker, 'getComponentFromName');
|
||||||
|
|
||||||
|
const promise = instance.present(knownOptions);
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
expect(deepLinker.getComponentFromName).not.toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
}).catch((err: Error) => {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load the component if its a string before using it', (done: Function) => {
|
||||||
|
const knownComponent = { };
|
||||||
|
const deepLinker = mockDeepLinker();
|
||||||
|
const knownOverlay = mockOverlay();
|
||||||
|
const componentName = 'my-component';
|
||||||
|
const instance = new OverlayProxy(mockApp(), componentName, mockConfig(), deepLinker);
|
||||||
|
const knownOptions = {};
|
||||||
|
|
||||||
|
spyOn(instance, 'getImplementation').and.returnValue(knownOverlay);
|
||||||
|
spyOn(deepLinker, 'getComponentFromName').and.returnValue(Promise.resolve(knownComponent));
|
||||||
|
|
||||||
|
const promise = instance.present(knownOptions);
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
expect(deepLinker.getComponentFromName).toHaveBeenCalledWith(componentName);
|
||||||
|
done();
|
||||||
|
}).catch((err: Error) => {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -12,6 +12,7 @@ import { Haptic } from '../tap-click/haptic';
|
|||||||
import { IonicApp } from '../components/app/app-root';
|
import { IonicApp } from '../components/app/app-root';
|
||||||
import { Keyboard } from '../platform/keyboard';
|
import { Keyboard } from '../platform/keyboard';
|
||||||
import { Menu } from '../components/menu/menu';
|
import { Menu } from '../components/menu/menu';
|
||||||
|
import { NavOptions } from '../navigation/nav-util';
|
||||||
import { NavControllerBase } from '../navigation/nav-controller-base';
|
import { NavControllerBase } from '../navigation/nav-controller-base';
|
||||||
import { OverlayPortal } from '../components/nav/overlay-portal';
|
import { OverlayPortal } from '../components/nav/overlay-portal';
|
||||||
import { PageTransition } from '../transitions/page-transition';
|
import { PageTransition } from '../transitions/page-transition';
|
||||||
@ -528,3 +529,12 @@ export function noop(): any { return 'noop'; };
|
|||||||
export function mockModuleLoader() {
|
export function mockModuleLoader() {
|
||||||
return { };
|
return { };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mockOverlay() {
|
||||||
|
return {
|
||||||
|
present: (opts?: NavOptions) => { return Promise.resolve(); },
|
||||||
|
dismiss: (data?: any, role?: any, navOptions?: NavOptions) => { return Promise.resolve(); },
|
||||||
|
onDidDismiss: (callback: Function) => { },
|
||||||
|
onWillDismiss: (callback: Function) => { }
|
||||||
|
};
|
||||||
|
}
|
@ -4,12 +4,17 @@ import { NgModuleLoader } from './ng-module-loader';
|
|||||||
|
|
||||||
export const LAZY_LOADED_TOKEN = new OpaqueToken('LZYCMP');
|
export const LAZY_LOADED_TOKEN = new OpaqueToken('LZYCMP');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ModuleLoader {
|
export class ModuleLoader {
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
_cfrMap = new Map<any, ComponentFactoryResolver>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _ngModuleLoader: NgModuleLoader,
|
private _ngModuleLoader: NgModuleLoader,
|
||||||
private _injector: Injector) {}
|
private _injector: Injector) {}
|
||||||
@ -20,16 +25,23 @@ export class ModuleLoader {
|
|||||||
|
|
||||||
const splitString = modulePath.split(SPLITTER);
|
const splitString = modulePath.split(SPLITTER);
|
||||||
|
|
||||||
return this._ngModuleLoader.load(splitString[0], splitString[1])
|
return this._ngModuleLoader.load(splitString[0], splitString[1]).then(loadedModule => {
|
||||||
.then(loadedModule => {
|
console.timeEnd(`ModuleLoader, load: ${modulePath}'`);
|
||||||
console.timeEnd(`ModuleLoader, load: ${modulePath}'`);
|
const ref = loadedModule.create(this._injector);
|
||||||
const ref = loadedModule.create(this._injector);
|
|
||||||
|
|
||||||
return {
|
const component = ref.injector.get(LAZY_LOADED_TOKEN);
|
||||||
componentFactoryResolver: ref.componentFactoryResolver,
|
|
||||||
component: ref.injector.get(LAZY_LOADED_TOKEN)
|
this._cfrMap.set(component, ref.componentFactoryResolver);
|
||||||
};
|
|
||||||
});
|
return {
|
||||||
|
componentFactoryResolver: ref.componentFactoryResolver,
|
||||||
|
component: component
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getComponentFactoryResolver(component: Type<any>) {
|
||||||
|
return this._cfrMap.get(component);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user