Skip to content

Commit

Permalink
refactor(nav): replace DynamicComponentLoader w/ ComponentFactory
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdbradley committed Jun 8, 2016
1 parent 46f6ee8 commit c1d09dd
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 128 deletions.
26 changes: 15 additions & 11 deletions src/components/modal/modal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Component, ComponentRef, ElementRef, DynamicComponentLoader, ViewChild, ViewContainerRef} from '@angular/core';
import {Component, ComponentRef, ElementRef, ViewChild, ViewContainerRef, ComponentResolver} from '@angular/core';

import {addSelector} from '../../config/bootstrap';
import {Animation} from '../../animations/animation';
Expand Down Expand Up @@ -148,10 +148,7 @@ export class Modal extends ViewController {
if (originalNgAfterViewInit) {
originalNgAfterViewInit();
}
this.instance.loadComponent().then( (componentRef: ComponentRef<any>) => {
this.setInstance(componentRef.instance);
done();
});
this.instance.loadComponent(done);
};
}
}
Expand All @@ -168,14 +165,21 @@ export class ModalCmp {

@ViewChild('viewport', {read: ViewContainerRef}) viewport: ViewContainerRef;

constructor(protected _loader: DynamicComponentLoader, protected _navParams: NavParams) {}
constructor(
private _compiler: ComponentResolver,
private _navParams: NavParams,
private _viewCtrl: ViewController
) {}

loadComponent(): Promise<ComponentRef<any>> {
let componentType = this._navParams.data.componentType;
addSelector(componentType, 'ion-page');
loadComponent(done: Function) {
addSelector(this._navParams.data.componentType, 'ion-modal-inner');

return this._loader.loadNextToLocation(componentType, this.viewport).then( (componentRef: ComponentRef<any>) => {
return componentRef;
this._compiler.resolveComponent(this._navParams.data.componentType).then((componentFactory) => {
let componentRef = this.viewport.createComponent(componentFactory, this.viewport.length, this.viewport.parentInjector);

this._viewCtrl.setInstance(componentRef.instance);

done();
});
}

Expand Down
57 changes: 0 additions & 57 deletions src/components/modal/test/modal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,65 +16,8 @@ export function run() {
});
});

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: any = {
loadNextToLocation: () => {}
};
let mockNavParams: any = {
data: {
componentType: function mockComponentType(){}
}
};
let mockComponentRef = {};

spyOn(mockLoader, "loadNextToLocation").and.returnValue(Promise.resolve(mockComponentRef));
let modalCmp = new ModalCmp(mockLoader, mockNavParams);
modalCmp.viewport = <any>"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';
Expand Down
69 changes: 33 additions & 36 deletions src/components/nav/nav-controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ViewContainerRef, DynamicComponentLoader, provide, ReflectiveInjector, ResolvedReflectiveProvider, ElementRef, NgZone, Renderer, Type, EventEmitter} from '@angular/core';
import {ViewContainerRef, ComponentResolver, ComponentRef, provide, ReflectiveInjector, ResolvedReflectiveProvider, ElementRef, NgZone, Renderer, EventEmitter} from '@angular/core';

import {addSelector} from '../../config/bootstrap';
import {App} from '../app/app';
Expand Down Expand Up @@ -184,11 +184,6 @@ export class NavController extends Ion {
*/
id: string;

/**
* @private
*/
providers: ResolvedReflectiveProvider[];

/**
* @private
*/
Expand Down Expand Up @@ -217,7 +212,7 @@ export class NavController extends Ion {
elementRef: ElementRef,
protected _zone: NgZone,
protected _renderer: Renderer,
protected _loader: DynamicComponentLoader
protected _compiler: ComponentResolver
) {
super(elementRef);

Expand All @@ -231,11 +226,6 @@ export class NavController extends Ion {

this.id = (++ctrlIds).toString();

// build a new injector for child ViewControllers to use
this.providers = ReflectiveInjector.resolve([
provide(NavController, {useValue: this})
]);

this.viewDidLoad = new EventEmitter();
this.viewWillEnter = new EventEmitter();
this.viewDidEnter = new EventEmitter();
Expand Down Expand Up @@ -268,12 +258,12 @@ export class NavController extends Ion {

/**
* Set the root for the current navigation stack.
* @param {Type} page The name of the component you want to push on the navigation stack.
* @param {Page} page The name of the component you want to push on the navigation stack.
* @param {object} [params={}] Any nav-params you want to pass along to the next view.
* @param {object} [opts={}] Any options you want to use pass to transtion.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
setRoot(page: Type, params?: any, opts?: NavOptions): Promise<any> {
setRoot(page: any, params?: any, opts?: NavOptions): Promise<any> {
return this.setPages([{page, params}], opts);
}

Expand Down Expand Up @@ -350,11 +340,11 @@ export class NavController extends Ion {
* }
*```
*
* @param {array<Type>} pages An arry of page components and their params to load in the stack.
* @param {array<Page>} pages An arry of page components and their params to load in the stack.
* @param {object} [opts={}] Nav options to go with this transition.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
setPages(pages: Array<{page: Type, params?: any}>, opts?: NavOptions): Promise<any> {
setPages(pages: Array<{page: any, params?: any}>, opts?: NavOptions): Promise<any> {
if (!pages || !pages.length) {
return Promise.resolve(false);
}
Expand Down Expand Up @@ -451,12 +441,12 @@ export class NavController extends Ion {
* }
* }
* ```
* @param {Type} page The page component class you want to push on to the navigation stack
* @param {Page} page The page component class you want to push on to the navigation stack
* @param {object} [params={}] Any nav-params you want to pass along to the next view
* @param {object} [opts={}] Nav options to go with this transition.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
push(page: Type, params?: any, opts?: NavOptions) {
push(page: any, params?: any, opts?: NavOptions) {
return this.insertPages(-1, [{page: page, params: params}], opts);
}

Expand Down Expand Up @@ -543,12 +533,12 @@ export class NavController extends Ion {
* This will insert the `Info` page into the second slot of our navigation stack.
*
* @param {number} insertIndex The index where to insert the page.
* @param {Type} page The component you want to insert into the nav stack.
* @param {Page} page The component you want to insert into the nav stack.
* @param {object} [params={}] Any nav-params you want to pass along to the next page.
* @param {object} [opts={}] Nav options to go with this transition.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
insert(insertIndex: number, page: Type, params?: any, opts?: NavOptions): Promise<any> {
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions): Promise<any> {
return this.insertPages(insertIndex, [{page: page, params: params}], opts);
}

Expand Down Expand Up @@ -576,11 +566,11 @@ export class NavController extends Ion {
* in and become the active page.
*
* @param {number} insertIndex The index where you want to insert the page.
* @param {array<{page: Type, params=: any}>} insertPages An array of objects, each with a `page` and optionally `params` property.
* @param {array<{page: Page, params=: any}>} insertPages An array of objects, each with a `page` and optionally `params` property.
* @param {object} [opts={}] Nav options to go with this transition.
* @returns {Promise} Returns a promise which is resolved when the transition has completed.
*/
insertPages(insertIndex: number, insertPages: Array<{page: Type, params?: any}>, opts?: NavOptions): Promise<any> {
insertPages(insertIndex: number, insertPages: Array<{page: any, params?: any}>, opts?: NavOptions): Promise<any> {
let views = insertPages.map(p => new ViewController(p.page, p.params));
return this._insertViews(insertIndex, views, opts);
}
Expand Down Expand Up @@ -1450,43 +1440,50 @@ export class NavController extends Ion {
return;
}

// add more providers to just this page
let providers = this.providers.concat(ReflectiveInjector.resolve([
provide(ViewController, {useValue: view}),
provide(NavParams, {useValue: view.getNavParams()})
]));

// automatically set "ion-page" selector
// TODO: see about having this set using ComponentFactory
addSelector(view.componentType, 'ion-page');

// load the page component inside the nav
this._loader.loadNextToLocation(view.componentType, this._viewport, providers).then(component => {
this._compiler.resolveComponent(view.componentType).then(componentFactory => {

// add more providers to just this page
let componentProviders = ReflectiveInjector.resolve([
provide(NavController, {useValue: this}),
provide(ViewController, {useValue: view}),
provide(NavParams, {useValue: view.getNavParams()})
]);

let childInjector = ReflectiveInjector.fromResolvedProviders(componentProviders, this._viewport.parentInjector);

let componentRef = componentFactory.create(childInjector, null, null);

this._viewport.insert(componentRef.hostView, this._viewport.length);

// a new ComponentRef has been created
// set the ComponentRef's instance to its ViewController
view.setInstance(component.instance);
view.setInstance(componentRef.instance);

// the component has been loaded, so call the view controller's loaded method to load any dependencies into the dom
view.loaded( () => {
view.loaded(() => {

// the ElementRef of the actual ion-page created
let pageElementRef = component.location;
let pageElementRef = componentRef.location;

// remember the ChangeDetectorRef for this ViewController
view.setChangeDetector(component.changeDetectorRef);
view.setChangeDetector(componentRef.changeDetectorRef);

// remember the ElementRef to the ion-page elementRef that was just created
view.setPageRef(pageElementRef);

// 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);

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();
componentRef.destroy();
});

if (!navbarContainerRef) {
Expand Down
6 changes: 3 additions & 3 deletions src/components/nav/nav-portal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Directive, ElementRef, Optional, NgZone, Renderer, DynamicComponentLoader, ViewContainerRef} from '@angular/core';
import {Directive, ElementRef, Optional, NgZone, Renderer, ComponentResolver, ViewContainerRef} from '@angular/core';

import {App} from '../app/app';
import {Config} from '../../config/config';
Expand All @@ -22,10 +22,10 @@ export class NavPortal extends NavController {
elementRef: ElementRef,
zone: NgZone,
renderer: Renderer,
loader: DynamicComponentLoader,
compiler: ComponentResolver,
viewPort: ViewContainerRef
) {
super(parent, app, config, keyboard, elementRef, zone, renderer, loader);
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler);
this.isPortal = true;
this.setViewport(viewPort);
}
Expand Down
8 changes: 1 addition & 7 deletions src/components/nav/nav-router.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {Directive, ViewContainerRef, DynamicComponentLoader, Attribute} from '@angular/core';
import {
RouterOutletMap,
Router} from '@angular/router';

import {Nav} from './nav';
import {ViewController} from './view-controller';
import {Directive} from '@angular/core';

/**
* @private
Expand Down
12 changes: 6 additions & 6 deletions src/components/nav/nav.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Component, ElementRef, ViewContainerRef, DynamicComponentLoader, Input, Optional, NgZone, Renderer, Type, ViewChild, ViewEncapsulation, AfterViewInit} from '@angular/core';
import {Component, ElementRef, ViewContainerRef, ComponentResolver, Input, Optional, NgZone, Renderer, ViewChild, ViewEncapsulation, AfterViewInit} from '@angular/core';

import {App} from '../app/app';
import {Config} from '../../config/config';
Expand Down Expand Up @@ -114,7 +114,7 @@ import {ViewController} from './view-controller';
encapsulation: ViewEncapsulation.None,
})
export class Nav extends NavController implements AfterViewInit {
private _root: Type;
private _root: any;
private _hasInit: boolean = false;

constructor(
Expand All @@ -126,9 +126,9 @@ export class Nav extends NavController implements AfterViewInit {
elementRef: ElementRef,
zone: NgZone,
renderer: Renderer,
loader: DynamicComponentLoader
compiler: ComponentResolver
) {
super(parent, app, config, keyboard, elementRef, zone, renderer, loader);
super(parent, app, config, keyboard, elementRef, zone, renderer, compiler);

if (viewCtrl) {
// an ion-nav can also act as an ion-page within a parent ion-nav
Expand Down Expand Up @@ -173,10 +173,10 @@ export class Nav extends NavController implements AfterViewInit {
* @input {Page} The Page component to load as the root page within this nav.
*/
@Input()
get root(): Type {
get root(): any {
return this._root;
}
set root(page: Type) {
set root(page: any) {
this._root = page;

if (this._hasInit) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/nav/view-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class ViewController {
*/
@Output() private _emitter: EventEmitter<any> = new EventEmitter();

constructor(public componentType?: Type, data?: any) {
constructor(public componentType?: any, data?: any) {
// 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 : {}));

Expand Down
Loading

0 comments on commit c1d09dd

Please sign in to comment.