|
1 | 1 | /** @module ng2_directives */ /** */
|
2 | 2 | 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 |
5 | 5 | } from '@angular/core';
|
6 |
| -import {Input} from "@angular/core"; |
7 |
| -import {ComponentRef} from "@angular/core"; |
8 |
| -import {Type} from "@angular/core"; |
9 | 6 |
|
10 | 7 | import {UIRouter} from "../../router";
|
11 | 8 | 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"; |
15 | 11 | import {Ng2ViewConfig} from "../statebuilders/views";
|
16 | 12 | import {ResolveContext} from "../../resolve/resolveContext";
|
17 | 13 | import {flattenR} from "../../common/common";
|
| 14 | +import {MergeInjector} from "../mergeInjector"; |
18 | 15 |
|
19 | 16 | /** @hidden */
|
20 | 17 | let id = 0;
|
@@ -132,14 +129,13 @@ export class UIView {
|
132 | 129 | @Input('ui-view') set _name(val: string) { this.name = val; }
|
133 | 130 | componentRef: ComponentRef<any>;
|
134 | 131 | deregister: Function;
|
135 |
| - uiViewData: any = {}; |
| 132 | + uiViewData: ActiveUIView = <any> {}; |
136 | 133 |
|
137 | 134 | static PARENT_INJECT = "UIView.PARENT_INJECT";
|
138 | 135 |
|
139 | 136 | constructor(
|
140 | 137 | public router: UIRouter,
|
141 | 138 | @Inject(UIView.PARENT_INJECT) public parent: ParentUIViewInject,
|
142 |
| - public compFactoryResolver: ComponentFactoryResolver, |
143 | 139 | public viewContainerRef: ViewContainerRef
|
144 | 140 | ) { }
|
145 | 141 |
|
@@ -170,56 +166,94 @@ export class UIView {
|
170 | 166 | this.disposeLast();
|
171 | 167 | }
|
172 | 168 |
|
| 169 | + /** |
| 170 | + * The view service is informing us of an updated ViewConfig |
| 171 | + * (usually because a transition activated some state and its views) |
| 172 | + */ |
173 | 173 | 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 |
174 | 176 | if (!config) return this.disposeLast();
|
175 |
| - if (!(config instanceof Ng2ViewConfig)) return; |
176 | 177 |
|
177 |
| - let uiViewData = this.uiViewData; |
178 |
| - let viewDecl = <Ng2ViewDeclaration> config.viewDecl; |
| 178 | + // Only care about Ng2 configs |
| 179 | + if (!(config instanceof Ng2ViewConfig)) return; |
179 | 180 |
|
180 | 181 | // 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 |
183 | 185 | 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); |
188 | 187 |
|
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 |
190 | 194 | 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); |
194 | 196 |
|
195 | 197 | // 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); |
220 | 207 |
|
221 |
| - let factory = this.compFactoryResolver.resolveComponentFactory(componentType); |
222 |
| - createComponent(factory); |
| 208 | + // TODO: wire uiCanExit and uiOnParamsChanged callbacks |
223 | 209 | }
|
224 |
| -} |
225 | 210 |
|
| 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 | +} |
0 commit comments