mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 04:53:58 +08:00
Feature/observable ext (#6670)
* refactor(nav-controller): refactor to better support dynamic component loading
This commit is contained in:
@ -6,14 +6,7 @@
|
|||||||
$modal-ios-background-color: $background-ios-color !default;
|
$modal-ios-background-color: $background-ios-color !default;
|
||||||
$modal-ios-border-radius: 5px !default;
|
$modal-ios-border-radius: 5px !default;
|
||||||
|
|
||||||
.modal ion-page {
|
|
||||||
background-color: $modal-ios-background-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-wrapper {
|
.modal-wrapper {
|
||||||
@media only screen and (min-width: 768px) and (min-height: 600px) {
|
background-color: $modal-ios-background-color;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
border-radius: $modal-ios-border-radius;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
$modal-md-background-color: $background-md-color !default;
|
$modal-md-background-color: $background-md-color !default;
|
||||||
|
|
||||||
|
|
||||||
.modal ion-page {
|
.modal-wrapper {
|
||||||
background-color: $modal-md-background-color;
|
background-color: $modal-md-background-color;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import {Component, DynamicComponentLoader, ViewChild, ViewContainerRef} from '@angular/core';
|
import {Component, ComponentRef, DynamicComponentLoader, ElementRef, ViewChild, ViewContainerRef} from '@angular/core';
|
||||||
|
|
||||||
|
import {windowDimensions} from '../../util/dom';
|
||||||
|
import {pascalCaseToDashCase} from '../../util/util';
|
||||||
import {NavParams} from '../nav/nav-params';
|
import {NavParams} from '../nav/nav-params';
|
||||||
import {ViewController} from '../nav/view-controller';
|
import {ViewController} from '../nav/view-controller';
|
||||||
import {Animation} from '../../animations/animation';
|
import {Animation} from '../../animations/animation';
|
||||||
@ -106,9 +108,12 @@ import {Transition, TransitionOptions} from '../../transitions/transition';
|
|||||||
*/
|
*/
|
||||||
export class Modal extends ViewController {
|
export class Modal extends ViewController {
|
||||||
|
|
||||||
|
public modalViewType: string;
|
||||||
|
|
||||||
constructor(componentType, data: any = {}) {
|
constructor(componentType, data: any = {}) {
|
||||||
data.componentType = componentType;
|
data.componentType = componentType;
|
||||||
super(ModalCmp, data);
|
super(ModalCmp, data);
|
||||||
|
this.modalViewType = componentType.name;
|
||||||
this.viewType = 'modal';
|
this.viewType = 'modal';
|
||||||
this.isOverlay = true;
|
this.isOverlay = true;
|
||||||
}
|
}
|
||||||
@ -129,6 +134,21 @@ export class Modal extends ViewController {
|
|||||||
return new Modal(componentType, data);
|
return new Modal(componentType, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override the load method and load our child component
|
||||||
|
loaded(done) {
|
||||||
|
// grab the instance, and proxy the ngAfterViewInit method
|
||||||
|
let originalNgAfterViewInit = this.instance.ngAfterViewInit;
|
||||||
|
|
||||||
|
this.instance.ngAfterViewInit = () => {
|
||||||
|
if ( originalNgAfterViewInit ) {
|
||||||
|
originalNgAfterViewInit();
|
||||||
|
}
|
||||||
|
this.instance.loadComponent().then( (componentRef: ComponentRef<any>) => {
|
||||||
|
this.setInstance(componentRef.instance);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -139,20 +159,22 @@ export class Modal extends ViewController {
|
|||||||
'<div #viewport></div>' +
|
'<div #viewport></div>' +
|
||||||
'</div>'
|
'</div>'
|
||||||
})
|
})
|
||||||
class ModalCmp {
|
export class ModalCmp {
|
||||||
|
|
||||||
@ViewChild('viewport', {read: ViewContainerRef}) viewport: ViewContainerRef;
|
@ViewChild('viewport', {read: ViewContainerRef}) viewport: ViewContainerRef;
|
||||||
|
|
||||||
constructor(private _loader: DynamicComponentLoader, private _navParams: NavParams, private _viewCtrl: ViewController) {}
|
constructor(protected _eleRef: ElementRef, protected _loader: DynamicComponentLoader, protected _navParams: NavParams, protected _viewCtrl: ViewController) {
|
||||||
|
}
|
||||||
|
|
||||||
onPageWillEnter() {
|
loadComponent(): Promise<ComponentRef<any>> {
|
||||||
this._loader.loadNextToLocation(this._navParams.data.componentType, this.viewport).then(componentRef => {
|
return this._loader.loadNextToLocation(this._navParams.data.componentType, this.viewport).then(componentRef => {
|
||||||
this._viewCtrl.setInstance(componentRef.instance);
|
return componentRef;
|
||||||
|
|
||||||
// manually fire onPageWillEnter() since ModalCmp's onPageWillEnter already happened
|
|
||||||
this._viewCtrl.willEnter();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
// intentionally kept empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -166,6 +188,13 @@ class ModalSlideIn extends Transition {
|
|||||||
let backdrop = new Animation(ele.querySelector('.backdrop'));
|
let backdrop = new Animation(ele.querySelector('.backdrop'));
|
||||||
backdrop.fromTo('opacity', 0.01, 0.4);
|
backdrop.fromTo('opacity', 0.01, 0.4);
|
||||||
let wrapper = new Animation(ele.querySelector('.modal-wrapper'));
|
let wrapper = new Animation(ele.querySelector('.modal-wrapper'));
|
||||||
|
let page = <HTMLElement> ele.querySelector('ion-page');
|
||||||
|
page.classList.add('show-page');
|
||||||
|
|
||||||
|
// auto-add page css className created from component JS class name
|
||||||
|
let cssClassName = pascalCaseToDashCase((<Modal>enteringView).modalViewType);
|
||||||
|
page.classList.add(cssClassName);
|
||||||
|
|
||||||
wrapper.fromTo('translateY', '100%', '0%');
|
wrapper.fromTo('translateY', '100%', '0%');
|
||||||
this
|
this
|
||||||
.element(enteringView.pageRef())
|
.element(enteringView.pageRef())
|
||||||
@ -191,10 +220,17 @@ class ModalSlideOut extends Transition {
|
|||||||
super(opts);
|
super(opts);
|
||||||
|
|
||||||
let ele = leavingView.pageRef().nativeElement;
|
let ele = leavingView.pageRef().nativeElement;
|
||||||
|
|
||||||
let backdrop = new Animation(ele.querySelector('.backdrop'));
|
let backdrop = new Animation(ele.querySelector('.backdrop'));
|
||||||
backdrop.fromTo('opacity', 0.4, 0.0);
|
backdrop.fromTo('opacity', 0.4, 0.0);
|
||||||
let wrapper = new Animation(ele.querySelector('.modal-wrapper'));
|
let wrapperEle = <HTMLElement> ele.querySelector('.modal-wrapper');
|
||||||
wrapper.fromTo('translateY', '0%', '100%');
|
let wrapperEleRect = wrapperEle.getBoundingClientRect();
|
||||||
|
let wrapper = new Animation(wrapperEle);
|
||||||
|
|
||||||
|
// height of the screen - top of the container tells us how much to scoot it down
|
||||||
|
// so it's off-screen
|
||||||
|
let screenDimensions = windowDimensions();
|
||||||
|
wrapper.fromTo('translateY', '0px', `${screenDimensions.height - wrapperEleRect.top}px`);
|
||||||
|
|
||||||
this
|
this
|
||||||
.element(leavingView.pageRef())
|
.element(leavingView.pageRef())
|
||||||
@ -216,6 +252,12 @@ class ModalMDSlideIn extends Transition {
|
|||||||
backdrop.fromTo('opacity', 0.01, 0.4);
|
backdrop.fromTo('opacity', 0.01, 0.4);
|
||||||
let wrapper = new Animation(ele.querySelector('.modal-wrapper'));
|
let wrapper = new Animation(ele.querySelector('.modal-wrapper'));
|
||||||
wrapper.fromTo('translateY', '40px', '0px');
|
wrapper.fromTo('translateY', '40px', '0px');
|
||||||
|
let page = <HTMLElement> ele.querySelector('ion-page');
|
||||||
|
page.classList.add('show-page');
|
||||||
|
|
||||||
|
// auto-add page css className created from component JS class name
|
||||||
|
let cssClassName = pascalCaseToDashCase((<Modal>enteringView).modalViewType);
|
||||||
|
page.classList.add(cssClassName);
|
||||||
|
|
||||||
this
|
this
|
||||||
.element(enteringView.pageRef())
|
.element(enteringView.pageRef())
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
$modal-wp-background-color: $background-wp-color !default;
|
$modal-wp-background-color: $background-wp-color !default;
|
||||||
|
|
||||||
|
|
||||||
.modal ion-page {
|
.modal-wrapper {
|
||||||
background-color: $modal-wp-background-color;
|
background-color: $modal-wp-background-color;
|
||||||
}
|
}
|
||||||
|
@ -68,8 +68,53 @@ class E2EPage {
|
|||||||
animation: 'my-fade-in'
|
animation: 'my-fade-in'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
presentNavigableModal(){
|
||||||
|
let modal = Modal.create(NavigableModal);
|
||||||
|
this.nav.present(modal);
|
||||||
|
//this.nav.push(NavigableModal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Page({
|
||||||
|
template: `
|
||||||
|
<ion-navbar *navbar>
|
||||||
|
<ion-title>Page One</ion-title>
|
||||||
|
</ion-navbar>
|
||||||
|
<ion-content>
|
||||||
|
<button full (click)="submit()">Submit</button>
|
||||||
|
</ion-content>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class NavigableModal{
|
||||||
|
constructor(private navController:NavController){
|
||||||
|
}
|
||||||
|
|
||||||
|
submit(){
|
||||||
|
this.navController.push(NavigableModal2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Page({
|
||||||
|
template: `
|
||||||
|
<ion-navbar *navbar>
|
||||||
|
<ion-title>Page Two</ion-title>
|
||||||
|
</ion-navbar>
|
||||||
|
<ion-content>
|
||||||
|
<button full (click)="submit()">Submit</button>
|
||||||
|
</ion-content>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class NavigableModal2{
|
||||||
|
constructor(private navController:NavController){
|
||||||
|
}
|
||||||
|
|
||||||
|
submit(){
|
||||||
|
this.navController.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Page({
|
@Page({
|
||||||
template: `
|
template: `
|
||||||
@ -105,6 +150,10 @@ class ModalPassData {
|
|||||||
this.viewCtrl.dismiss(this.data);
|
this.viewCtrl.dismiss(this.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPageLoaded(){
|
||||||
|
console.log("ModalPassData onPageLoaded fired");
|
||||||
|
}
|
||||||
|
|
||||||
onPageWillEnter(){
|
onPageWillEnter(){
|
||||||
console.log("ModalPassData onPagewillEnter fired");
|
console.log("ModalPassData onPagewillEnter fired");
|
||||||
}
|
}
|
||||||
@ -280,15 +329,26 @@ class ModalFirstPage {
|
|||||||
push() {
|
push() {
|
||||||
let page = ModalSecondPage;
|
let page = ModalSecondPage;
|
||||||
let params = { id: 8675309, myData: [1,2,3,4] };
|
let params = { id: 8675309, myData: [1,2,3,4] };
|
||||||
let opts = { animation: 'ios-transition' };
|
|
||||||
|
|
||||||
this.nav.push(page, params, opts);
|
this.nav.push(page, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
dismiss() {
|
dismiss() {
|
||||||
this.nav.rootNav.pop();
|
this.nav.rootNav.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPageLoaded(){
|
||||||
|
console.log("ModalFirstPage OnPageLoaded fired");
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageWillEnter(){
|
||||||
|
console.log("ModalFirstPage onPageWillEnter fired");
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageDidEnter(){
|
||||||
|
console.log("ModalFirstPage onPageDidEnter fired");
|
||||||
|
}
|
||||||
|
|
||||||
openActionSheet() {
|
openActionSheet() {
|
||||||
let actionSheet = ActionSheet.create({
|
let actionSheet = ActionSheet.create({
|
||||||
buttons: [
|
buttons: [
|
||||||
@ -352,12 +412,21 @@ class ModalFirstPage {
|
|||||||
`
|
`
|
||||||
})
|
})
|
||||||
class ModalSecondPage {
|
class ModalSecondPage {
|
||||||
constructor(
|
constructor(private nav: NavController, params: NavParams) {
|
||||||
private nav: NavController,
|
|
||||||
params: NavParams
|
|
||||||
) {
|
|
||||||
console.log('Second page params:', params);
|
console.log('Second page params:', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPageLoaded(){
|
||||||
|
console.log("ModalSecondPage onPageLoaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageWillEnter(){
|
||||||
|
console.log("ModalSecondPage onPageWillEnter");
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageDidEnter(){
|
||||||
|
console.log("ModalSecondPage onPageDidEnter");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -398,4 +467,4 @@ class FadeOut extends Transition {
|
|||||||
.before.addClass('show-page');
|
.before.addClass('show-page');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Transition.register('my-fade-out', FadeOut);
|
Transition.register('my-fade-out', FadeOut);
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<ion-navbar *navbar>
|
<ion-navbar *navbar>
|
||||||
<ion-title>Modals</ion-title>
|
<ion-title>Modals</ion-title>
|
||||||
</ion-navbar>
|
</ion-navbar>
|
||||||
@ -7,6 +6,9 @@
|
|||||||
<p>
|
<p>
|
||||||
<button (click)="presentModal()">Present modal, pass params</button>
|
<button (click)="presentModal()">Present modal, pass params</button>
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<button (click)="presentNavigableModal()">Present modal, push page</button>
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<button class="e2eOpenModal" (click)="presentModalChildNav()">Present modal w/ child ion-nav</button>
|
<button class="e2eOpenModal" (click)="presentModalChildNav()">Present modal w/ child ion-nav</button>
|
||||||
</p>
|
</p>
|
||||||
|
100
src/components/modal/test/modal.spec.ts
Normal file
100
src/components/modal/test/modal.spec.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import {Modal, ModalCmp, Page, NavController, ViewController} from '../../../../src';
|
||||||
|
|
||||||
|
export function run() {
|
||||||
|
describe('Modal', () => {
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
|
||||||
|
it('should have the correct properties on modal view controller instance', () => {
|
||||||
|
let modalViewController = Modal.create(ComponentToPresent);
|
||||||
|
expect(modalViewController.modalViewType).toEqual("ComponentToPresent");
|
||||||
|
expect(modalViewController.componentType).toEqual(ModalCmp);
|
||||||
|
expect(modalViewController.viewType).toEqual("modal");
|
||||||
|
expect(modalViewController.isOverlay).toEqual(true);
|
||||||
|
expect(modalViewController instanceof ViewController).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loaded', () => {
|
||||||
|
it('should call done after loading component and call original ngAfterViewInit method', (done) => {
|
||||||
|
// arrange
|
||||||
|
let modal = new Modal({}, {});
|
||||||
|
let mockInstance = {
|
||||||
|
ngAfterViewInit: () => {},
|
||||||
|
loadComponent: () => {}
|
||||||
|
};
|
||||||
|
let mockComponentRef = {
|
||||||
|
instance: "someData"
|
||||||
|
};
|
||||||
|
modal.instance = mockInstance;
|
||||||
|
|
||||||
|
let ngAfterViewInitSpy = spyOn(mockInstance, "ngAfterViewInit");
|
||||||
|
spyOn(mockInstance, "loadComponent").and.returnValue(Promise.resolve(mockComponentRef));
|
||||||
|
|
||||||
|
let doneCallback = () => {
|
||||||
|
// assert
|
||||||
|
expect(ngAfterViewInitSpy).toHaveBeenCalled();
|
||||||
|
expect(modal.instance).toEqual("someData");
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
// act
|
||||||
|
modal.loaded(doneCallback);
|
||||||
|
// (angular calls ngAfterViewInit, we're not testing angular so manually call it)
|
||||||
|
mockInstance.ngAfterViewInit();
|
||||||
|
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ModalCmp', () => {
|
||||||
|
|
||||||
|
it('should return a componentRef object after loading component', (done) => {
|
||||||
|
// arrange
|
||||||
|
let mockLoader = {
|
||||||
|
loadNextToLocation: () => {}
|
||||||
|
};
|
||||||
|
let mockNavParams = {
|
||||||
|
data: {
|
||||||
|
componentType: "myComponentType"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mockComponentRef = {};
|
||||||
|
|
||||||
|
spyOn(mockLoader, "loadNextToLocation").and.returnValue(Promise.resolve(mockComponentRef));
|
||||||
|
let modalCmp = new ModalCmp(null, mockLoader, mockNavParams, null);
|
||||||
|
modalCmp.viewport = "mockViewport";
|
||||||
|
|
||||||
|
// act
|
||||||
|
modalCmp.loadComponent().then(loadedComponentRef => {
|
||||||
|
// assert
|
||||||
|
expect(loadedComponentRef).toEqual(mockComponentRef);
|
||||||
|
expect(mockLoader.loadNextToLocation).toHaveBeenCalledWith(mockNavParams.data.componentType, modalCmp.viewport);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const STATE_ACTIVE = 'active';
|
||||||
|
const STATE_INACTIVE = 'inactive';
|
||||||
|
const STATE_INIT_ENTER = 'init_enter';
|
||||||
|
const STATE_INIT_LEAVE = 'init_leave';
|
||||||
|
const STATE_TRANS_ENTER = 'trans_enter';
|
||||||
|
const STATE_TRANS_LEAVE = 'trans_leave';
|
||||||
|
const STATE_REMOVE = 'remove';
|
||||||
|
const STATE_REMOVE_AFTER_TRANS = 'remove_after_trans';
|
||||||
|
const STATE_FORCE_ACTIVE = 'force_active';
|
||||||
|
|
||||||
|
|
||||||
|
let componentToPresentSpy = {
|
||||||
|
_ionicProjectContent: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
@Page({
|
||||||
|
template: `<div class="myComponent"></div>`
|
||||||
|
})
|
||||||
|
class ComponentToPresent{
|
||||||
|
constructor(){
|
||||||
|
}
|
||||||
|
}
|
@ -818,10 +818,10 @@ export class NavController extends Ion {
|
|||||||
if (!parentNav['_tabs']) {
|
if (!parentNav['_tabs']) {
|
||||||
// Tabs can be a parent, but it is not a collection of views
|
// Tabs can be a parent, but it is not a collection of views
|
||||||
// only we're looking for an actual NavController w/ stack of views
|
// only we're looking for an actual NavController w/ stack of views
|
||||||
leavingView.willLeave();
|
leavingView.fireWillLeave();
|
||||||
|
|
||||||
return parentNav.pop(opts).then((rtnVal: boolean) => {
|
return parentNav.pop(opts).then((rtnVal: boolean) => {
|
||||||
leavingView.didLeave();
|
leavingView.fireDidLeave();
|
||||||
return rtnVal;
|
return rtnVal;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -918,7 +918,7 @@ export class NavController extends Ion {
|
|||||||
// set that it is the init leaving view
|
// set that it is the init leaving view
|
||||||
// the first view to be removed, it should init leave
|
// the first view to be removed, it should init leave
|
||||||
view.state = STATE_INIT_LEAVE;
|
view.state = STATE_INIT_LEAVE;
|
||||||
view.willUnload();
|
view.fireWillUnload();
|
||||||
|
|
||||||
// from the index of the leaving view, go backwards and
|
// from the index of the leaving view, go backwards and
|
||||||
// find the first view that is inactive so it can be the entering
|
// find the first view that is inactive so it can be the entering
|
||||||
@ -951,8 +951,8 @@ export class NavController extends Ion {
|
|||||||
// remove views that have been set to be removed, but not
|
// remove views that have been set to be removed, but not
|
||||||
// apart of any transitions that will eventually happen
|
// apart of any transitions that will eventually happen
|
||||||
this._views.filter(v => v.state === STATE_REMOVE).forEach(view => {
|
this._views.filter(v => v.state === STATE_REMOVE).forEach(view => {
|
||||||
view.willLeave();
|
view.fireWillLeave();
|
||||||
view.didLeave();
|
view.fireDidLeave();
|
||||||
this._views.splice(this.indexOf(view), 1);
|
this._views.splice(this.indexOf(view), 1);
|
||||||
view.destroy();
|
view.destroy();
|
||||||
});
|
});
|
||||||
@ -986,7 +986,7 @@ export class NavController extends Ion {
|
|||||||
if (!enteringView) {
|
if (!enteringView) {
|
||||||
// if no entering view then create a bogus one
|
// if no entering view then create a bogus one
|
||||||
enteringView = new ViewController();
|
enteringView = new ViewController();
|
||||||
enteringView.loaded();
|
enteringView.fireLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Async steps to complete a transition
|
/* Async steps to complete a transition
|
||||||
@ -1042,19 +1042,8 @@ export class NavController extends Ion {
|
|||||||
this.setTransitioning(true, 500);
|
this.setTransitioning(true, 500);
|
||||||
|
|
||||||
this.loadPage(enteringView, null, opts, () => {
|
this.loadPage(enteringView, null, opts, () => {
|
||||||
if (enteringView.onReady) {
|
enteringView.fireLoaded();
|
||||||
// this entering view needs to wait for it to be ready
|
this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done);
|
||||||
// this is used by Tabs to wait for the first page of
|
|
||||||
// the first selected tab to be loaded
|
|
||||||
enteringView.onReady(() => {
|
|
||||||
enteringView.loaded();
|
|
||||||
this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
enteringView.loaded();
|
|
||||||
this._postRender(transId, enteringView, leavingView, isAlreadyTransitioning, opts, done);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1112,13 +1101,13 @@ export class NavController extends Ion {
|
|||||||
if (leavingView.fireOtherLifecycles) {
|
if (leavingView.fireOtherLifecycles) {
|
||||||
// only fire entering lifecycle if the leaving
|
// only fire entering lifecycle if the leaving
|
||||||
// view hasn't explicitly set not to
|
// view hasn't explicitly set not to
|
||||||
enteringView.willEnter();
|
enteringView.fireWillEnter();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enteringView.fireOtherLifecycles) {
|
if (enteringView.fireOtherLifecycles) {
|
||||||
// only fire leaving lifecycle if the entering
|
// only fire leaving lifecycle if the entering
|
||||||
// view hasn't explicitly set not to
|
// view hasn't explicitly set not to
|
||||||
leavingView.willLeave();
|
leavingView.fireWillLeave();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -1224,13 +1213,13 @@ export class NavController extends Ion {
|
|||||||
if (leavingView.fireOtherLifecycles) {
|
if (leavingView.fireOtherLifecycles) {
|
||||||
// only fire entering lifecycle if the leaving
|
// only fire entering lifecycle if the leaving
|
||||||
// view hasn't explicitly set not to
|
// view hasn't explicitly set not to
|
||||||
enteringView.didEnter();
|
enteringView.fireDidEnter();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enteringView.fireOtherLifecycles) {
|
if (enteringView.fireOtherLifecycles) {
|
||||||
// only fire leaving lifecycle if the entering
|
// only fire leaving lifecycle if the entering
|
||||||
// view hasn't explicitly set not to
|
// view hasn't explicitly set not to
|
||||||
leavingView.didLeave();
|
leavingView.fireDidLeave();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1440,56 +1429,61 @@ export class NavController extends Ion {
|
|||||||
|
|
||||||
// load the page component inside the nav
|
// load the page component inside the nav
|
||||||
this._loader.loadNextToLocation(view.componentType, this._viewport, providers).then(component => {
|
this._loader.loadNextToLocation(view.componentType, this._viewport, providers).then(component => {
|
||||||
// the ElementRef of the actual ion-page created
|
|
||||||
let pageElementRef = component.location;
|
|
||||||
|
|
||||||
// a new ComponentRef has been created
|
// a new ComponentRef has been created
|
||||||
// set the ComponentRef's instance to its ViewController
|
// set the ComponentRef's instance to its ViewController
|
||||||
view.setInstance(component.instance);
|
view.setInstance(component.instance);
|
||||||
|
|
||||||
|
// the component has been loaded, so call the view controller's loaded method to load any dependencies into the dom
|
||||||
|
view.loaded( () => {
|
||||||
|
|
||||||
|
// the ElementRef of the actual ion-page created
|
||||||
|
let pageElementRef = component.location;
|
||||||
|
|
||||||
// remember the ChangeDetectorRef for this ViewController
|
// remember the ChangeDetectorRef for this ViewController
|
||||||
view.setChangeDetector(component.changeDetectorRef);
|
view.setChangeDetector(component.changeDetectorRef);
|
||||||
|
|
||||||
// remember the ElementRef to the ion-page elementRef that was just created
|
// remember the ElementRef to the ion-page elementRef that was just created
|
||||||
view.setPageRef(pageElementRef);
|
view.setPageRef(pageElementRef);
|
||||||
|
|
||||||
// auto-add page css className created from component JS class name
|
// auto-add page css className created from component JS class name
|
||||||
let cssClassName = pascalCaseToDashCase(view.componentType['name']);
|
let cssClassName = pascalCaseToDashCase(view.componentType['name']);
|
||||||
this._renderer.setElementClass(pageElementRef.nativeElement, cssClassName, true);
|
this._renderer.setElementClass(pageElementRef.nativeElement, cssClassName, true);
|
||||||
|
|
||||||
view.onDestroy(() => {
|
|
||||||
// ensure the element is cleaned up for when the view pool reuses this element
|
|
||||||
this._renderer.setElementAttribute(pageElementRef.nativeElement, 'class', null);
|
|
||||||
this._renderer.setElementAttribute(pageElementRef.nativeElement, 'style', null);
|
|
||||||
component.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!navbarContainerRef) {
|
|
||||||
// there was not a navbar container ref already provided
|
|
||||||
// so use the location of the actual navbar template
|
|
||||||
navbarContainerRef = view.getNavbarViewRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
// find a navbar template if one is in the page
|
|
||||||
let navbarTemplateRef = view.getNavbarTemplateRef();
|
|
||||||
|
|
||||||
// check if we have both a navbar ViewContainerRef and a template
|
|
||||||
if (navbarContainerRef && navbarTemplateRef) {
|
|
||||||
// let's now create the navbar view
|
|
||||||
let navbarViewRef = navbarContainerRef.createEmbeddedView(navbarTemplateRef);
|
|
||||||
|
|
||||||
view.onDestroy(() => {
|
view.onDestroy(() => {
|
||||||
// manually destroy the navbar when the page is destroyed
|
// ensure the element is cleaned up for when the view pool reuses this element
|
||||||
navbarViewRef.destroy();
|
this._renderer.setElementAttribute(pageElementRef.nativeElement, 'class', null);
|
||||||
|
this._renderer.setElementAttribute(pageElementRef.nativeElement, 'style', null);
|
||||||
|
component.destroy();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// options may have had a postLoad method
|
if (!navbarContainerRef) {
|
||||||
// used mainly by tabs
|
// there was not a navbar container ref already provided
|
||||||
opts.postLoad && opts.postLoad(view);
|
// so use the location of the actual navbar template
|
||||||
|
navbarContainerRef = view.getNavbarViewRef();
|
||||||
|
}
|
||||||
|
|
||||||
// our job is done here
|
// find a navbar template if one is in the page
|
||||||
done(view);
|
let navbarTemplateRef = view.getNavbarTemplateRef();
|
||||||
|
|
||||||
|
// check if we have both a navbar ViewContainerRef and a template
|
||||||
|
if (navbarContainerRef && navbarTemplateRef) {
|
||||||
|
// let's now create the navbar view
|
||||||
|
let navbarViewRef = navbarContainerRef.createEmbeddedView(navbarTemplateRef);
|
||||||
|
|
||||||
|
view.onDestroy(() => {
|
||||||
|
// manually destroy the navbar when the page is destroyed
|
||||||
|
navbarViewRef.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// options may have had a postLoad method
|
||||||
|
// used mainly by tabs
|
||||||
|
opts.postLoad && opts.postLoad(view);
|
||||||
|
|
||||||
|
// our job is done here
|
||||||
|
done(view);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,20 +269,20 @@ export function run() {
|
|||||||
view4.state = STATE_ACTIVE;
|
view4.state = STATE_ACTIVE;
|
||||||
nav._views = [view1, view2, view3, view4];
|
nav._views = [view1, view2, view3, view4];
|
||||||
|
|
||||||
spyOn(view1, 'willLeave');
|
spyOn(view1, 'fireWillLeave');
|
||||||
spyOn(view1, 'didLeave');
|
spyOn(view1, 'fireDidLeave');
|
||||||
spyOn(view1, 'destroy');
|
spyOn(view1, 'destroy');
|
||||||
|
|
||||||
spyOn(view2, 'willLeave');
|
spyOn(view2, 'fireWillLeave');
|
||||||
spyOn(view2, 'didLeave');
|
spyOn(view2, 'fireDidLeave');
|
||||||
spyOn(view2, 'destroy');
|
spyOn(view2, 'destroy');
|
||||||
|
|
||||||
spyOn(view3, 'willLeave');
|
spyOn(view3, 'fireWillLeave');
|
||||||
spyOn(view3, 'didLeave');
|
spyOn(view3, 'fireDidLeave');
|
||||||
spyOn(view3, 'destroy');
|
spyOn(view3, 'destroy');
|
||||||
|
|
||||||
spyOn(view4, 'willLeave');
|
spyOn(view4, 'fireWillLeave');
|
||||||
spyOn(view4, 'didLeave');
|
spyOn(view4, 'fireDidLeave');
|
||||||
spyOn(view4, 'destroy');
|
spyOn(view4, 'destroy');
|
||||||
|
|
||||||
nav._remove(1, 3);
|
nav._remove(1, 3);
|
||||||
@ -292,20 +292,20 @@ export function run() {
|
|||||||
expect(view3.state).toBe(STATE_REMOVE);
|
expect(view3.state).toBe(STATE_REMOVE);
|
||||||
expect(view4.state).toBe(STATE_INIT_LEAVE);
|
expect(view4.state).toBe(STATE_INIT_LEAVE);
|
||||||
|
|
||||||
expect(view1.willLeave).not.toHaveBeenCalled();
|
expect(view1.fireWillLeave).not.toHaveBeenCalled();
|
||||||
expect(view1.didLeave).not.toHaveBeenCalled();
|
expect(view1.fireDidLeave).not.toHaveBeenCalled();
|
||||||
expect(view1.destroy).not.toHaveBeenCalled();
|
expect(view1.destroy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
expect(view2.willLeave).toHaveBeenCalled();
|
expect(view2.fireWillLeave).toHaveBeenCalled();
|
||||||
expect(view2.didLeave).toHaveBeenCalled();
|
expect(view2.fireDidLeave).toHaveBeenCalled();
|
||||||
expect(view2.destroy).toHaveBeenCalled();
|
expect(view2.destroy).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(view3.willLeave).toHaveBeenCalled();
|
expect(view3.fireWillLeave).toHaveBeenCalled();
|
||||||
expect(view3.didLeave).toHaveBeenCalled();
|
expect(view3.fireDidLeave).toHaveBeenCalled();
|
||||||
expect(view3.destroy).toHaveBeenCalled();
|
expect(view3.destroy).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(view4.willLeave).not.toHaveBeenCalled();
|
expect(view4.fireWillLeave).not.toHaveBeenCalled();
|
||||||
expect(view4.didLeave).not.toHaveBeenCalled();
|
expect(view4.fireDidLeave).not.toHaveBeenCalled();
|
||||||
expect(view4.destroy).not.toHaveBeenCalled();
|
expect(view4.destroy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -412,11 +412,11 @@ export function run() {
|
|||||||
var done = () => {};
|
var done = () => {};
|
||||||
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
||||||
|
|
||||||
spyOn(enteringView, 'willEnter');
|
spyOn(enteringView, 'fireWillEnter');
|
||||||
|
|
||||||
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
||||||
|
|
||||||
expect(enteringView.willEnter).toHaveBeenCalled();
|
expect(enteringView.fireWillEnter).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call willEnter on entering view when it is being preloaded', () => {
|
it('should not call willEnter on entering view when it is being preloaded', () => {
|
||||||
@ -428,11 +428,11 @@ export function run() {
|
|||||||
var done = () => {};
|
var done = () => {};
|
||||||
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
||||||
|
|
||||||
spyOn(enteringView, 'willEnter');
|
spyOn(enteringView, 'fireWillEnter');
|
||||||
|
|
||||||
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
||||||
|
|
||||||
expect(enteringView.willEnter).not.toHaveBeenCalled();
|
expect(enteringView.fireWillEnter).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call willLeave on leaving view', () => {
|
it('should call willLeave on leaving view', () => {
|
||||||
@ -442,11 +442,11 @@ export function run() {
|
|||||||
var done = () => {};
|
var done = () => {};
|
||||||
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
||||||
|
|
||||||
spyOn(leavingView, 'willLeave');
|
spyOn(leavingView, 'fireWillLeave');
|
||||||
|
|
||||||
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
||||||
|
|
||||||
expect(leavingView.willLeave).toHaveBeenCalled();
|
expect(leavingView.fireWillLeave).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call willEnter when the leaving view has fireOtherLifecycles not true', () => {
|
it('should not call willEnter when the leaving view has fireOtherLifecycles not true', () => {
|
||||||
@ -456,15 +456,15 @@ export function run() {
|
|||||||
var done = () => {};
|
var done = () => {};
|
||||||
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
||||||
|
|
||||||
spyOn(enteringView, 'willEnter');
|
spyOn(enteringView, 'fireWillEnter');
|
||||||
spyOn(leavingView, 'willLeave');
|
spyOn(leavingView, 'fireWillLeave');
|
||||||
|
|
||||||
leavingView.fireOtherLifecycles = false;
|
leavingView.fireOtherLifecycles = false;
|
||||||
|
|
||||||
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
||||||
|
|
||||||
expect(enteringView.willEnter).not.toHaveBeenCalled();
|
expect(enteringView.fireWillEnter).not.toHaveBeenCalled();
|
||||||
expect(leavingView.willLeave).toHaveBeenCalled();
|
expect(leavingView.fireWillLeave).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call willLeave when the entering view has fireOtherLifecycles not true', () => {
|
it('should not call willLeave when the entering view has fireOtherLifecycles not true', () => {
|
||||||
@ -474,15 +474,15 @@ export function run() {
|
|||||||
var done = () => {};
|
var done = () => {};
|
||||||
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
||||||
|
|
||||||
spyOn(enteringView, 'willEnter');
|
spyOn(enteringView, 'fireWillEnter');
|
||||||
spyOn(leavingView, 'willLeave');
|
spyOn(leavingView, 'fireWillLeave');
|
||||||
|
|
||||||
enteringView.fireOtherLifecycles = false;
|
enteringView.fireOtherLifecycles = false;
|
||||||
|
|
||||||
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
||||||
|
|
||||||
expect(enteringView.willEnter).toHaveBeenCalled();
|
expect(enteringView.fireWillEnter).toHaveBeenCalled();
|
||||||
expect(leavingView.willLeave).not.toHaveBeenCalled();
|
expect(leavingView.fireWillLeave).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call willLeave on leaving view when it is being preloaded', () => {
|
it('should not call willLeave on leaving view when it is being preloaded', () => {
|
||||||
@ -494,11 +494,11 @@ export function run() {
|
|||||||
var done = () => {};
|
var done = () => {};
|
||||||
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
nav._beforeTrans = () => {}; //prevent running beforeTrans for tests
|
||||||
|
|
||||||
spyOn(leavingView, 'willLeave');
|
spyOn(leavingView, 'fireWillLeave');
|
||||||
|
|
||||||
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
nav._postRender(1, enteringView, leavingView, false, navOptions, done);
|
||||||
|
|
||||||
expect(leavingView.willLeave).not.toHaveBeenCalled();
|
expect(leavingView.fireWillLeave).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set animate false when preloading', () => {
|
it('should set animate false when preloading', () => {
|
||||||
@ -725,13 +725,13 @@ export function run() {
|
|||||||
let doneCalled = false;
|
let doneCalled = false;
|
||||||
let done = () => {doneCalled = true;}
|
let done = () => {doneCalled = true;}
|
||||||
|
|
||||||
spyOn(enteringView, 'didEnter');
|
spyOn(enteringView, 'fireDidEnter');
|
||||||
spyOn(leavingView, 'didLeave');
|
spyOn(leavingView, 'fireDidLeave');
|
||||||
|
|
||||||
nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
|
nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
|
||||||
|
|
||||||
expect(enteringView.didEnter).toHaveBeenCalled();
|
expect(enteringView.fireDidEnter).toHaveBeenCalled();
|
||||||
expect(leavingView.didLeave).toHaveBeenCalled();
|
expect(leavingView.fireDidLeave).toHaveBeenCalled();
|
||||||
expect(doneCalled).toBe(true);
|
expect(doneCalled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -745,13 +745,13 @@ export function run() {
|
|||||||
let doneCalled = false;
|
let doneCalled = false;
|
||||||
let done = () => {doneCalled = true;}
|
let done = () => {doneCalled = true;}
|
||||||
|
|
||||||
spyOn(enteringView, 'didEnter');
|
spyOn(enteringView, 'fireDidEnter');
|
||||||
spyOn(leavingView, 'didLeave');
|
spyOn(leavingView, 'fireDidLeave');
|
||||||
|
|
||||||
nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
|
nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
|
||||||
|
|
||||||
expect(enteringView.didEnter).not.toHaveBeenCalled();
|
expect(enteringView.fireDidEnter).not.toHaveBeenCalled();
|
||||||
expect(leavingView.didLeave).not.toHaveBeenCalled();
|
expect(leavingView.fireDidLeave).not.toHaveBeenCalled();
|
||||||
expect(doneCalled).toBe(true);
|
expect(doneCalled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -765,13 +765,13 @@ export function run() {
|
|||||||
|
|
||||||
enteringView.fireOtherLifecycles = false;
|
enteringView.fireOtherLifecycles = false;
|
||||||
|
|
||||||
spyOn(enteringView, 'didEnter');
|
spyOn(enteringView, 'fireDidEnter');
|
||||||
spyOn(leavingView, 'didLeave');
|
spyOn(leavingView, 'fireDidLeave');
|
||||||
|
|
||||||
nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
|
nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
|
||||||
|
|
||||||
expect(enteringView.didEnter).toHaveBeenCalled();
|
expect(enteringView.fireDidEnter).toHaveBeenCalled();
|
||||||
expect(leavingView.didLeave).not.toHaveBeenCalled();
|
expect(leavingView.fireDidLeave).not.toHaveBeenCalled();
|
||||||
expect(doneCalled).toBe(true);
|
expect(doneCalled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -785,13 +785,13 @@ export function run() {
|
|||||||
|
|
||||||
leavingView.fireOtherLifecycles = false;
|
leavingView.fireOtherLifecycles = false;
|
||||||
|
|
||||||
spyOn(enteringView, 'didEnter');
|
spyOn(enteringView, 'fireDidEnter');
|
||||||
spyOn(leavingView, 'didLeave');
|
spyOn(leavingView, 'fireDidLeave');
|
||||||
|
|
||||||
nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
|
nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
|
||||||
|
|
||||||
expect(enteringView.didEnter).not.toHaveBeenCalled();
|
expect(enteringView.fireDidEnter).not.toHaveBeenCalled();
|
||||||
expect(leavingView.didLeave).toHaveBeenCalled();
|
expect(leavingView.fireDidLeave).toHaveBeenCalled();
|
||||||
expect(doneCalled).toBe(true);
|
expect(doneCalled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -803,13 +803,13 @@ export function run() {
|
|||||||
let doneCalled = false;
|
let doneCalled = false;
|
||||||
let done = () => {doneCalled = true;}
|
let done = () => {doneCalled = true;}
|
||||||
|
|
||||||
spyOn(enteringView, 'didEnter');
|
spyOn(enteringView, 'fireDidEnter');
|
||||||
spyOn(leavingView, 'didLeave');
|
spyOn(leavingView, 'fireDidLeave');
|
||||||
|
|
||||||
nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
|
nav._afterTrans(enteringView, leavingView, navOpts, hasCompleted, done);
|
||||||
|
|
||||||
expect(enteringView.didEnter).not.toHaveBeenCalled();
|
expect(enteringView.fireDidEnter).not.toHaveBeenCalled();
|
||||||
expect(leavingView.didLeave).not.toHaveBeenCalled();
|
expect(leavingView.fireDidLeave).not.toHaveBeenCalled();
|
||||||
expect(doneCalled).toBe(true);
|
expect(doneCalled).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
133
src/components/nav/test/view-controller.spec.ts
Normal file
133
src/components/nav/test/view-controller.spec.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import {LifeCycleEvent, ViewController} from '../../../../src';
|
||||||
|
|
||||||
|
export function run() {
|
||||||
|
describe('ViewController', () => {
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if ( subscription ){
|
||||||
|
subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loaded', () => {
|
||||||
|
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||||
|
// arrange
|
||||||
|
let viewController = new ViewController(FakePage);
|
||||||
|
subscription = viewController.didLoad.subscribe((event:LifeCycleEvent) => {
|
||||||
|
// assert
|
||||||
|
expect(event).toEqual(null);
|
||||||
|
done();
|
||||||
|
}, err => {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// act
|
||||||
|
viewController.fireLoaded();
|
||||||
|
}, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('willEnter', () => {
|
||||||
|
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||||
|
// arrange
|
||||||
|
let viewController = new ViewController(FakePage);
|
||||||
|
subscription = viewController.willEnter.subscribe((event:LifeCycleEvent) => {
|
||||||
|
// assert
|
||||||
|
expect(event).toEqual(null);
|
||||||
|
done();
|
||||||
|
}, err => {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// act
|
||||||
|
viewController.fireWillEnter();
|
||||||
|
}, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('didEnter', () => {
|
||||||
|
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||||
|
// arrange
|
||||||
|
let viewController = new ViewController(FakePage);
|
||||||
|
subscription = viewController.didEnter.subscribe((event:LifeCycleEvent) => {
|
||||||
|
// assert
|
||||||
|
expect(event).toEqual(null);
|
||||||
|
done();
|
||||||
|
}, err => {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// act
|
||||||
|
viewController.fireDidEnter();
|
||||||
|
}, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('willLeave', () => {
|
||||||
|
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||||
|
// arrange
|
||||||
|
let viewController = new ViewController(FakePage);
|
||||||
|
subscription = viewController.willLeave.subscribe((event:LifeCycleEvent) => {
|
||||||
|
// assert
|
||||||
|
expect(event).toEqual(null);
|
||||||
|
done();
|
||||||
|
}, err => {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// act
|
||||||
|
viewController.fireWillLeave();
|
||||||
|
}, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('didLeave', () => {
|
||||||
|
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||||
|
// arrange
|
||||||
|
let viewController = new ViewController(FakePage);
|
||||||
|
subscription = viewController.didLeave.subscribe((event:LifeCycleEvent) => {
|
||||||
|
// assert
|
||||||
|
expect(event).toEqual(null);
|
||||||
|
done();
|
||||||
|
}, err => {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// act
|
||||||
|
viewController.fireDidLeave();
|
||||||
|
}, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('willUnload', () => {
|
||||||
|
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||||
|
// arrange
|
||||||
|
let viewController = new ViewController(FakePage);
|
||||||
|
subscription = viewController.willUnload.subscribe((event:LifeCycleEvent) => {
|
||||||
|
expect(event).toEqual(null);
|
||||||
|
done();
|
||||||
|
}, err => {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// act
|
||||||
|
viewController.fireWillUnload();
|
||||||
|
}, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('destroy', () => {
|
||||||
|
it('should emit LifeCycleEvent when called with component data', (done) => {
|
||||||
|
// arrange
|
||||||
|
let viewController = new ViewController(FakePage);
|
||||||
|
subscription = viewController.didUnload.subscribe((event:LifeCycleEvent) => {
|
||||||
|
// assert
|
||||||
|
expect(event).toEqual(null);
|
||||||
|
done();
|
||||||
|
}, err => {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// act
|
||||||
|
viewController.destroy();
|
||||||
|
}, 10000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let subscription = null;
|
||||||
|
class FakePage{}
|
||||||
|
}
|
@ -37,6 +37,14 @@ export class ViewController {
|
|||||||
private _cd: ChangeDetectorRef;
|
private _cd: ChangeDetectorRef;
|
||||||
protected _nav: NavController;
|
protected _nav: NavController;
|
||||||
|
|
||||||
|
didLoad: EventEmitter<any>;
|
||||||
|
willEnter: EventEmitter<any>;
|
||||||
|
didEnter: EventEmitter<any>;
|
||||||
|
willLeave: EventEmitter<any>;
|
||||||
|
didLeave: EventEmitter<any>;
|
||||||
|
willUnload: EventEmitter<any>;
|
||||||
|
didUnload: EventEmitter<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -62,11 +70,6 @@ export class ViewController {
|
|||||||
*/
|
*/
|
||||||
viewType: string = '';
|
viewType: string = '';
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
onReady: Function;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* If this is currently the active view, then set to false
|
* If this is currently the active view, then set to false
|
||||||
@ -97,6 +100,14 @@ export class ViewController {
|
|||||||
constructor(public componentType?: Type, data?: any) {
|
constructor(public componentType?: Type, data?: any) {
|
||||||
// passed in data could be NavParams, but all we care about is its data object
|
// passed in data could be NavParams, but all we care about is its data object
|
||||||
this.data = (data instanceof NavParams ? data.data : (isPresent(data) ? data : {}));
|
this.data = (data instanceof NavParams ? data.data : (isPresent(data) ? data : {}));
|
||||||
|
|
||||||
|
this.didLoad = new EventEmitter();
|
||||||
|
this.willEnter = new EventEmitter();
|
||||||
|
this.didEnter = new EventEmitter();
|
||||||
|
this.willLeave = new EventEmitter();
|
||||||
|
this.didLeave = new EventEmitter();
|
||||||
|
this.willUnload = new EventEmitter();
|
||||||
|
this.didUnload = new EventEmitter();
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(generatorOrNext?: any): any {
|
subscribe(generatorOrNext?: any): any {
|
||||||
@ -472,6 +483,16 @@ export class ViewController {
|
|||||||
isLoaded(): boolean {
|
isLoaded(): boolean {
|
||||||
return this._loaded;
|
return this._loaded;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The loaded method is used to load any dynamic content/components
|
||||||
|
* into the dom before proceeding with the transition. If a component needs
|
||||||
|
* dynamic component loading, extending ViewController and overriding
|
||||||
|
* this method is a good option
|
||||||
|
* @param {function} done is a callback that must be called when async loading/actions are completed
|
||||||
|
*/
|
||||||
|
loaded(done: (() => any)) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@ -481,8 +502,9 @@ export class ViewController {
|
|||||||
* to put your setup code for the view; however, it is not the
|
* to put your setup code for the view; however, it is not the
|
||||||
* recommended method to use when a view becomes active.
|
* recommended method to use when a view becomes active.
|
||||||
*/
|
*/
|
||||||
loaded() {
|
fireLoaded() {
|
||||||
this._loaded = true;
|
this._loaded = true;
|
||||||
|
this.didLoad.emit(null);
|
||||||
ctrlFn(this, 'onPageLoaded');
|
ctrlFn(this, 'onPageLoaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,7 +512,7 @@ export class ViewController {
|
|||||||
* @private
|
* @private
|
||||||
* The view is about to enter and become the active view.
|
* The view is about to enter and become the active view.
|
||||||
*/
|
*/
|
||||||
willEnter() {
|
fireWillEnter() {
|
||||||
if (this._cd) {
|
if (this._cd) {
|
||||||
// ensure this has been re-attached to the change detector
|
// ensure this has been re-attached to the change detector
|
||||||
this._cd.reattach();
|
this._cd.reattach();
|
||||||
@ -498,7 +520,7 @@ export class ViewController {
|
|||||||
// detect changes before we run any user code
|
// detect changes before we run any user code
|
||||||
this._cd.detectChanges();
|
this._cd.detectChanges();
|
||||||
}
|
}
|
||||||
|
this.willEnter.emit(null);
|
||||||
ctrlFn(this, 'onPageWillEnter');
|
ctrlFn(this, 'onPageWillEnter');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -507,9 +529,10 @@ export class ViewController {
|
|||||||
* The view has fully entered and is now the active view. This
|
* The view has fully entered and is now the active view. This
|
||||||
* will fire, whether it was the first load or loaded from the cache.
|
* will fire, whether it was the first load or loaded from the cache.
|
||||||
*/
|
*/
|
||||||
didEnter() {
|
fireDidEnter() {
|
||||||
let navbar = this.getNavbar();
|
let navbar = this.getNavbar();
|
||||||
navbar && navbar.didEnter();
|
navbar && navbar.didEnter();
|
||||||
|
this.didEnter.emit(null);
|
||||||
ctrlFn(this, 'onPageDidEnter');
|
ctrlFn(this, 'onPageDidEnter');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,7 +540,8 @@ export class ViewController {
|
|||||||
* @private
|
* @private
|
||||||
* The view has is about to leave and no longer be the active view.
|
* The view has is about to leave and no longer be the active view.
|
||||||
*/
|
*/
|
||||||
willLeave() {
|
fireWillLeave() {
|
||||||
|
this.willLeave.emit(null);
|
||||||
ctrlFn(this, 'onPageWillLeave');
|
ctrlFn(this, 'onPageWillLeave');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,7 +550,8 @@ export class ViewController {
|
|||||||
* The view has finished leaving and is no longer the active view. This
|
* The view has finished leaving and is no longer the active view. This
|
||||||
* will fire, whether it is cached or unloaded.
|
* will fire, whether it is cached or unloaded.
|
||||||
*/
|
*/
|
||||||
didLeave() {
|
fireDidLeave() {
|
||||||
|
this.didLeave.emit(null);
|
||||||
ctrlFn(this, 'onPageDidLeave');
|
ctrlFn(this, 'onPageDidLeave');
|
||||||
|
|
||||||
// when this is not the active page
|
// when this is not the active page
|
||||||
@ -538,7 +563,8 @@ export class ViewController {
|
|||||||
* @private
|
* @private
|
||||||
* The view is about to be destroyed and have its elements removed.
|
* The view is about to be destroyed and have its elements removed.
|
||||||
*/
|
*/
|
||||||
willUnload() {
|
fireWillUnload() {
|
||||||
|
this.willUnload.emit(null);
|
||||||
ctrlFn(this, 'onPageWillUnload');
|
ctrlFn(this, 'onPageWillUnload');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,6 +579,7 @@ export class ViewController {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
|
this.didUnload.emit(null);
|
||||||
ctrlFn(this, 'onPageDidUnload');
|
ctrlFn(this, 'onPageDidUnload');
|
||||||
|
|
||||||
for (var i = 0; i < this._destroys.length; i++) {
|
for (var i = 0; i < this._destroys.length; i++) {
|
||||||
@ -564,6 +591,10 @@ export class ViewController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LifeCycleEvent {
|
||||||
|
componentType?: any;
|
||||||
|
}
|
||||||
|
|
||||||
function ctrlFn(viewCtrl: ViewController, fnName: string) {
|
function ctrlFn(viewCtrl: ViewController, fnName: string) {
|
||||||
if (viewCtrl.instance && viewCtrl.instance[fnName]) {
|
if (viewCtrl.instance && viewCtrl.instance[fnName]) {
|
||||||
try {
|
try {
|
||||||
|
@ -252,7 +252,7 @@ export class Tabs extends Ion {
|
|||||||
viewCtrl.setContent(this);
|
viewCtrl.setContent(this);
|
||||||
viewCtrl.setContentRef(_elementRef);
|
viewCtrl.setContentRef(_elementRef);
|
||||||
|
|
||||||
viewCtrl.onReady = (done) => {
|
viewCtrl.loaded = (done) => {
|
||||||
this._onReady = done;
|
this._onReady = done;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -357,11 +357,11 @@ export class Tabs extends Ion {
|
|||||||
let deselectedPage;
|
let deselectedPage;
|
||||||
if (deselectedTab) {
|
if (deselectedTab) {
|
||||||
deselectedPage = deselectedTab.getActive();
|
deselectedPage = deselectedTab.getActive();
|
||||||
deselectedPage && deselectedPage.willLeave();
|
deselectedPage && deselectedPage.fireWillLeave();
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectedPage = selectedTab.getActive();
|
let selectedPage = selectedTab.getActive();
|
||||||
selectedPage && selectedPage.willEnter();
|
selectedPage && selectedPage.fireWillEnter();
|
||||||
|
|
||||||
selectedTab.load(opts, () => {
|
selectedTab.load(opts, () => {
|
||||||
|
|
||||||
@ -382,8 +382,8 @@ export class Tabs extends Ion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedPage && selectedPage.didEnter();
|
selectedPage && selectedPage.fireDidEnter();
|
||||||
deselectedPage && deselectedPage.didLeave();
|
deselectedPage && deselectedPage.fireDidLeave();
|
||||||
|
|
||||||
if (this._onReady) {
|
if (this._onReady) {
|
||||||
this._onReady();
|
this._onReady();
|
||||||
|
Reference in New Issue
Block a user