Skip to content

Commit 37241e7

Browse files
feat(ng2.UIView): Use merged NgModule/ParentComp to inject routed component
feat(ng2.NgModule): Add the root NgModule to root resolve
1 parent c3857b5 commit 37241e7

File tree

5 files changed

+114
-52
lines changed

5 files changed

+114
-52
lines changed

src/ng2/directives/uiView.ts

+84-50
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
/** @module ng2_directives */ /** */
22
import {
3-
Component, ComponentFactoryResolver, ComponentFactory,
4-
ViewContainerRef, ReflectiveInjector, InputMetadata, ComponentMetadata, ViewChild
3+
Component, ComponentFactoryResolver, ViewContainerRef, Input, ComponentRef, Type,
4+
ReflectiveInjector, InputMetadata, ComponentMetadata, ViewChild, Injector, Inject
55
} from '@angular/core';
6-
import {Input} from "@angular/core";
7-
import {ComponentRef} from "@angular/core";
8-
import {Type} from "@angular/core";
96

107
import {UIRouter} from "../../router";
118
import {trace} from "../../common/trace";
12-
import {Inject} from "@angular/core";
13-
import {ViewContext, ViewConfig} from "../../view/interface";
14-
import {Ng2ViewDeclaration} from "../interface";
9+
import {ViewContext, ViewConfig, ActiveUIView} from "../../view/interface";
10+
import {NG2_INJECTOR_TOKEN} from "../interface";
1511
import {Ng2ViewConfig} from "../statebuilders/views";
1612
import {ResolveContext} from "../../resolve/resolveContext";
1713
import {flattenR} from "../../common/common";
14+
import {MergeInjector} from "../mergeInjector";
1815

1916
/** @hidden */
2017
let id = 0;
@@ -132,14 +129,13 @@ export class UIView {
132129
@Input('ui-view') set _name(val: string) { this.name = val; }
133130
componentRef: ComponentRef<any>;
134131
deregister: Function;
135-
uiViewData: any = {};
132+
uiViewData: ActiveUIView = <any> {};
136133

137134
static PARENT_INJECT = "UIView.PARENT_INJECT";
138135

139136
constructor(
140137
public router: UIRouter,
141138
@Inject(UIView.PARENT_INJECT) public parent: ParentUIViewInject,
142-
public compFactoryResolver: ComponentFactoryResolver,
143139
public viewContainerRef: ViewContainerRef
144140
) { }
145141

@@ -170,56 +166,94 @@ export class UIView {
170166
this.disposeLast();
171167
}
172168

169+
/**
170+
* The view service is informing us of an updated ViewConfig
171+
* (usually because a transition activated some state and its views)
172+
*/
173173
viewConfigUpdated(config: ViewConfig) {
174+
// The config may be undefined if there is nothing currently targeting this UIView.
175+
// Dispose the current component, if there is one
174176
if (!config) return this.disposeLast();
175-
if (!(config instanceof Ng2ViewConfig)) return;
176177

177-
let uiViewData = this.uiViewData;
178-
let viewDecl = <Ng2ViewDeclaration> config.viewDecl;
178+
// Only care about Ng2 configs
179+
if (!(config instanceof Ng2ViewConfig)) return;
179180

180181
// The "new" viewconfig is already applied, so exit early
181-
if (uiViewData.config === config) return;
182-
// This is a new viewconfig. Destroy the old component
182+
if (this.uiViewData.config === config) return;
183+
184+
// This is a new ViewConfig. Dispose the previous component
183185
this.disposeLast();
184-
trace.traceUIViewConfigUpdated(uiViewData, config && config.viewDecl.$context);
185-
uiViewData.config = config;
186-
// The config may be undefined if there is nothing state currently targeting this UIView.
187-
if (!config) return;
186+
trace.traceUIViewConfigUpdated(this.uiViewData, config && config.viewDecl.$context);
188187

189-
// Map resolves to "useValue providers"
188+
this.applyUpdatedConfig(config);
189+
}
190+
191+
applyUpdatedConfig(config: Ng2ViewConfig) {
192+
this.uiViewData.config = config;
193+
// Create the Injector for the routed component
190194
let context = new ResolveContext(config.path);
191-
let resolvables = context.getTokens().map(token => context.getResolvable(token)).filter(r => r.resolved);
192-
let rawProviders = resolvables.map(r => ({ provide: r.token, useValue: r.data }));
193-
rawProviders.push({ provide: UIView.PARENT_INJECT, useValue: { context: config.viewDecl.$context, fqn: uiViewData.fqn } });
195+
let componentInjector = this.getComponentInjector(context);
194196

195197
// Get the component class from the view declaration. TODO: allow promises?
196-
let componentType = <any> viewDecl.component;
197-
198-
let createComponent = (factory: ComponentFactory<any>) => {
199-
let parentInjector = this.viewContainerRef.injector;
200-
let childInjector = ReflectiveInjector.resolveAndCreate(rawProviders, parentInjector);
201-
let ref = this.componentRef = this.componentTarget.createComponent(factory, undefined, childInjector);
202-
203-
// TODO: wire uiCanExit and uiOnParamsChanged callbacks
204-
205-
let bindings = viewDecl['bindings'] || {};
206-
var addResolvable = (tuple: InputMapping) => ({
207-
prop: tuple.prop,
208-
resolvable: context.getResolvable(bindings[tuple.prop] || tuple.token)
209-
});
210-
211-
// Supply resolve data to matching @Input('prop') or inputs: ['prop']
212-
let inputTuples = ng2ComponentInputs(componentType);
213-
inputTuples.map(addResolvable)
214-
.filter(tuple => tuple.resolvable && tuple.resolvable.resolved)
215-
.forEach(tuple => { ref.instance[tuple.prop] = tuple.resolvable.data });
216-
217-
// Initiate change detection for the newly created component
218-
ref.changeDetectorRef.detectChanges();
219-
};
198+
let componentClass = config.viewDecl.component;
199+
200+
// Create the component
201+
let compFactoryResolver = componentInjector.get(ComponentFactoryResolver);
202+
let compFactory = compFactoryResolver.resolveComponentFactory(componentClass);
203+
this.componentRef = this.componentTarget.createComponent(compFactory, undefined, componentInjector);
204+
205+
// Wire resolves to @Input()s
206+
this.applyInputBindings(this.componentRef, context, componentClass);
220207

221-
let factory = this.compFactoryResolver.resolveComponentFactory(componentType);
222-
createComponent(factory);
208+
// TODO: wire uiCanExit and uiOnParamsChanged callbacks
223209
}
224-
}
225210

211+
/**
212+
* Creates a new Injector for a routed component.
213+
*
214+
* Adds resolve values to the Injector
215+
* Adds providers from the NgModule for the state
216+
* Adds providers from the parent Component in the component tree
217+
* Adds a PARENT_INJECT view context object
218+
*
219+
* @returns an Injector
220+
*/
221+
getComponentInjector(context: ResolveContext): Injector {
222+
// Map resolves to "useValue: providers"
223+
let resolvables = context.getTokens().map(token => context.getResolvable(token)).filter(r => r.resolved);
224+
let newProviders = resolvables.map(r => ({ provide: r.token, useValue: r.data }));
225+
226+
var parentInject = { context: this.uiViewData.config.viewDecl.$context, fqn: this.uiViewData.fqn };
227+
newProviders.push({ provide: UIView.PARENT_INJECT, useValue: parentInject });
228+
229+
let parentComponentInjector = this.viewContainerRef.injector;
230+
let moduleInjector = context.getResolvable(NG2_INJECTOR_TOKEN).data;
231+
let mergedParentInjector = new MergeInjector(moduleInjector, parentComponentInjector);
232+
233+
return ReflectiveInjector.resolveAndCreate(newProviders, mergedParentInjector);
234+
}
235+
236+
/**
237+
* Supplies component inputs with resolve data
238+
*
239+
* Finds component inputs which match resolves (by name) and sets the input value
240+
* to the resolve data.
241+
*/
242+
applyInputBindings(ref: ComponentRef<any>, context: ResolveContext, componentClass) {
243+
let bindings = this.uiViewData.config.viewDecl['bindings'] || {};
244+
245+
var addResolvable = (tuple: InputMapping) => ({
246+
prop: tuple.prop,
247+
resolvable: context.getResolvable(bindings[tuple.prop] || tuple.token)
248+
});
249+
250+
// Supply resolve data to matching @Input('prop') or inputs: ['prop']
251+
let inputTuples = ng2ComponentInputs(componentClass);
252+
inputTuples.map(addResolvable)
253+
.filter(tuple => tuple.resolvable && tuple.resolvable.resolved)
254+
.forEach(tuple => { ref.instance[tuple.prop] = tuple.resolvable.data });
255+
256+
// Initiate change detection for the newly created component
257+
ref.changeDetectorRef.detectChanges();
258+
}
259+
}

src/ng2/interface.ts

+2
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,5 @@ export interface Ng2Component {
341341
*/
342342
uiCanExit(): HookResult;
343343
}
344+
345+
export const NG2_INJECTOR_TOKEN = {};

src/ng2/mergeInjector.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {Injector} from "@angular/core";
2+
3+
export class MergeInjector implements Injector {
4+
static NOT_FOUND = {};
5+
private injectors: Injector[];
6+
constructor(...injectors: Injector[]) {
7+
if (injectors.length < 2) throw new Error("pass at least two injectors");
8+
this.injectors = injectors;
9+
}
10+
11+
get(token: any, notFoundValue?: any): any {
12+
for (let i = 0; i < this.injectors.length; i++) {
13+
let val = this.injectors[i].get(token, MergeInjector.NOT_FOUND);
14+
if (val !== MergeInjector.NOT_FOUND) return val;
15+
}
16+
17+
if (arguments.length >= 2) return notFoundValue;
18+
19+
// This will throw the DI Injector error
20+
this.injectors[0].get(token);
21+
}
22+
}

src/ng2/providers.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,13 @@ import {UrlRouter} from "../url/urlRouter";
5757
import {ViewService} from "../view/view";
5858
import {UIView, ParentUIViewInject} from "./directives/uiView";
5959
import {ng2ViewsBuilder, Ng2ViewConfig} from "./statebuilders/views";
60-
import {Ng2ViewDeclaration} from "./interface";
60+
import {Ng2ViewDeclaration, NG2_INJECTOR_TOKEN} from "./interface";
6161
import {UIRouterConfig} from "./uiRouterConfig";
6262
import {Globals} from "../globals";
6363
import {UIRouterLocation} from "./location";
6464
import {services} from "../common/coreservices";
6565
import {ProviderLike} from "../state/interface";
66+
import {Resolvable} from "../resolve/resolvable";
6667

6768
let uiRouterFactory = (routerConfig: UIRouterConfig, location: UIRouterLocation, injector: Injector) => {
6869
services.$injector.get = injector.get.bind(injector);
@@ -74,6 +75,9 @@ let uiRouterFactory = (routerConfig: UIRouterConfig, location: UIRouterLocation,
7475
router.stateRegistry.decorator('views', ng2ViewsBuilder);
7576

7677
router.stateRegistry.stateQueue.autoFlush(router.stateService);
78+
79+
let ng2InjectorResolvable = new Resolvable(NG2_INJECTOR_TOKEN, () => injector, null, { when: "EAGER" }, injector);
80+
router.stateRegistry.root().resolvables.push(ng2InjectorResolvable);
7781

7882
setTimeout(() => {
7983
routerConfig.configure(router);

src/ng2/routerModule.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function UIRouterModule(moduleMetaData: UIRouterModuleMetadata) {
5656
let components = states.map(state => state.views || { $default: state })
5757
.map(viewObj => Object.keys(viewObj).map(key => viewObj[key].component))
5858
.reduce((acc, arr) => acc.concat(arr), [])
59-
.filter(x => typeof x === 'function');
59+
.filter(x => typeof x === 'function' && x !== UIView);
6060

6161
moduleMetaData.imports = <any[]> (moduleMetaData.imports || []).concat(_UIRouterModule).reduce(uniqR, []);
6262
moduleMetaData.declarations = <any[]> (moduleMetaData.declarations || []).concat(components).reduce(uniqR, []);

0 commit comments

Comments
 (0)