diff --git a/src/core.ts b/src/core.ts index a013740d6..dafa9f409 100644 --- a/src/core.ts +++ b/src/core.ts @@ -8,5 +8,6 @@ export * from "./state/module"; export * from "./transition/module"; export * from "./url/module"; export * from "./view/module"; +export * from "./globals"; export { UIRouter } from "./router"; diff --git a/src/ng1/viewDirective.ts b/src/ng1/viewDirective.ts index 9bfe85c2d..24d6e80ba 100644 --- a/src/ng1/viewDirective.ts +++ b/src/ng1/viewDirective.ts @@ -1,7 +1,7 @@ /** @module ng1_directives */ /** for typedoc */ "use strict"; import {extend, map, unnestR, filter} from "../common/common"; -import {isDefined, isFunction} from "../common/predicates"; +import {isDefined, isFunction, isString} from "../common/predicates"; import {trace} from "../common/trace"; import {ActiveUIView} from "../view/interface"; import {Ng1ViewConfig} from "./viewsBuilder"; @@ -357,7 +357,7 @@ function $ViewDirectiveFill ( $compile, $controller, $transitions, $view, } // Wait for the component to appear in the DOM - if (cfg.viewDecl.component) { + if (isString(cfg.viewDecl.component)) { let cmp = cfg.viewDecl.component; let kebobName = kebobString(cmp); let getComponentController = () => { diff --git a/src/ng1/viewsBuilder.ts b/src/ng1/viewsBuilder.ts index c2eba3172..d8da74f6f 100644 --- a/src/ng1/viewsBuilder.ts +++ b/src/ng1/viewsBuilder.ts @@ -49,7 +49,8 @@ export function ng1ViewsBuilder(state: State) { config.templateProvider = ['$injector', function($injector) { const resolveFor = key => config.bindings && config.bindings[key] || key; const prefix = angular.version.minor >= 3 ? "::" : ""; - let attrs = getComponentInputs($injector, config.component).map(key => `${kebobString(key)}='${prefix}$resolve.${resolveFor(key)}'`).join(" "); + let attrs = getComponentInputs($injector, config.component) + .map(key => `${kebobString(key)}='${prefix}$resolve.${resolveFor(key)}'`).join(" "); let kebobName = kebobString(config.component); return `<${kebobName} ${attrs}>`; }]; diff --git a/src/ng2/componentUtil.ts b/src/ng2/componentUtil.ts new file mode 100644 index 000000000..bdf9399eb --- /dev/null +++ b/src/ng2/componentUtil.ts @@ -0,0 +1,27 @@ +import {InputMetadata, ComponentMetadata} from "angular2/core"; + +export const ng2ComponentInputs = (ng2CompClass) => { + /** Get "@Input('foo') _foo" inputs */ + let props = Reflect['getMetadata']('propMetadata', ng2CompClass); + let _props = Object.keys(props || {}) + // -> { string, anno[] } tuples + .map(key => ({ key, annoArr: props[key] })) + // -> to { string, anno } tuples + .reduce((acc, tuple) => acc.concat(tuple.annoArr.map(anno => ({ key: tuple.key, anno }))), []) + // Only Inputs + .filter(tuple => tuple.anno instanceof InputMetadata) + // If they have a bindingPropertyName, i.e. "@Input('foo') _foo", then foo, else _foo + .map(tuple => ({ resolve: tuple.anno.bindingPropertyName || tuple.key, prop: tuple.key })); + + /** Get "inputs: ['foo']" inputs */ + let inputs = Reflect['getMetadata']('annotations', ng2CompClass) + // Find the ComponentMetadata class annotation + .filter(x => x instanceof ComponentMetadata && !!x.inputs) + // Get the .inputs string array + .map(x => x.inputs) + // Flatten + .reduce((acc, arr) => acc.concat(arr), []) + .map(input => ({ resolve: input, prop: input })); + + return _props.concat(inputs); +}; \ No newline at end of file diff --git a/src/ng2/providers.ts b/src/ng2/providers.ts index d6d835de9..6efc88b71 100644 --- a/src/ng2/providers.ts +++ b/src/ng2/providers.ts @@ -110,9 +110,7 @@ export const UIROUTER_PROVIDERS: Provider[] = [ provide(UIRouterGlobals, { useFactory: (r: UIRouter) => { return r.globals; }, deps: [UIRouter]}), - provide(UiView.INJECT.context, { useFactory: (r: StateRegistry) => { return r.root(); }, deps: [StateRegistry]} ), - - provide(UiView.INJECT.fqn, { useValue: null }) + provide(UiView.PARENT_INJECT, { useFactory: (r: StateRegistry) => { return { fqn: null, context: r.root() } }, deps: [StateRegistry]} ) ]; diff --git a/src/ng2/uiSref.ts b/src/ng2/uiSref.ts index 1e18b1c54..6ea994997 100644 --- a/src/ng2/uiSref.ts +++ b/src/ng2/uiSref.ts @@ -4,8 +4,7 @@ import {Directive, Inject, Input} from "angular2/core"; import {Optional} from "angular2/core"; import {ElementRef} from "angular2/core"; import {Renderer} from "angular2/core"; -import {UiView} from "./uiView"; -import {ViewContext} from "../view/interface"; +import {UiView, ParentUiViewInject} from "./uiView"; import {extend} from "../common/common"; /** @hidden */ @@ -69,7 +68,7 @@ export class UiSref { constructor( private _router: UIRouter, - @Inject(UiView.INJECT.context) public context: ViewContext, + @Inject(UiView.PARENT_INJECT) public parent: ParentUiViewInject, @Optional() private _anchorUiSref: AnchorUiSref ) { } @@ -88,7 +87,7 @@ export class UiSref { } getOptions() { - let defOpts = { relative: this.context.name, inherit: true }; + let defOpts = { relative: this.parent && this.parent.context && this.parent.context.name, inherit: true }; return extend(defOpts, this.options || {}); } diff --git a/src/ng2/uiView.ts b/src/ng2/uiView.ts index c21dd083d..1af1a616a 100755 --- a/src/ng2/uiView.ts +++ b/src/ng2/uiView.ts @@ -11,6 +11,7 @@ import {trace} from "../common/trace"; import {Inject} from "angular2/core"; import {ViewContext, ViewConfig} from "../view/interface"; import {Ng2ViewDeclaration} from "./interface"; +import {ng2ComponentInputs} from "./componentUtil"; /** @hidden */ let id = 0; @@ -23,6 +24,12 @@ const getProviders = (injector) => { return providers; }; +// These are provide()d as the string UiView.PARENT_INJECT +export interface ParentUiViewInject { + context: ViewContext; + fqn: string; +} + /** * A UI-Router viewport directive, which is filled in by a view (component) on a state. * @@ -84,44 +91,39 @@ const getProviders = (injector) => { //
// //
- //
ui-view #{{uiViewData.id}} created by '{{ parentContext.name || "(root)" }}' state
- //
name: (absolute) '{{uiViewData.fqn}}' (contextual) '{{uiViewData.name}}@{{parentContext.name}}'
- //
currently filled by: '{{(uiViewData.config && uiViewData.config.viewDecl.$context) || 'empty...'}}'
+ //
ui-view #{{uiViewData?.id}} created by '{{ parentContext?.name || "(root)" }}' state
+ //
name: (absolute) '{{uiViewData?.fqn}}' (contextual) '{{uiViewData?.name}}@{{parentContext?.name}}'
+ //
currently filled by: '{{(uiViewData?.config && uiViewData?.config?.viewDecl?.$context) || 'empty...'}}'
//
// //
` }) export class UiView { @Input() name: string; - @Input() set 'ui-view'(val) { this.name = val; } - + @Input('ui-view') set _name(val) { this.name = val; } componentRef: ComponentRef; deregister: Function; uiViewData: any = {}; - static INJECT = { - fqn: "UiView.parentFQN", - context: "UiView.parentContext" - }; + static PARENT_INJECT = "UiView.PARENT_INJECT"; constructor( public router: UIRouter, - @Inject(UiView.INJECT.context) public parentContext: ViewContext, - @Inject(UiView.INJECT.fqn) public parentFqn: string, + @Inject(UiView.PARENT_INJECT) public parent: ParentUiViewInject, public dcl: DynamicComponentLoader, public elementRef: ElementRef, public injector: Injector ) { } ngOnInit() { - let parentFqn = this.parentFqn; + let parentFqn = this.parent.fqn; let name = this.name || '$default'; this.uiViewData = { id: id++, name: name, fqn: parentFqn ? parentFqn + "." + name : name, - creationContext: this.parentContext, + creationContext: this.parent.context, configUpdated: this.viewConfigUpdated.bind(this), config: undefined }; @@ -131,10 +133,11 @@ export class UiView { disposeLast() { if (this.componentRef) this.componentRef.dispose(); + this.componentRef = null; } ngOnDestroy() { - this.deregister(); + if (this.deregister) this.deregister(); this.disposeLast(); } @@ -159,17 +162,26 @@ export class UiView { let rc = config.node.resolveContext; let resolvables = rc.getResolvables(); let rawProviders = Object.keys(resolvables).map(key => provide(key, { useValue: resolvables[key].data })); - rawProviders.push(provide(UiView.INJECT.context, { useValue: config.viewDecl.$context })); - rawProviders.push(provide(UiView.INJECT.fqn, { useValue: uiViewData.fqn })); + rawProviders.push(provide(UiView.PARENT_INJECT, { useValue: { context: config.viewDecl.$context, fqn: uiViewData.fqn } })); let providers = Injector.resolve(rawProviders); - let exclusions = [UiView.INJECT.context, UiView.INJECT.fqn]; + let exclusions = [UiView.PARENT_INJECT]; providers = getProviders(injector).filter(x => exclusions.indexOf(x.key.displayName) === -1).concat(providers); - // The 'controller' should be a Component class - // TODO: pull from 'component' declaration, do not require template. let component = viewDecl.component; - dcl.loadIntoLocation(component, elementRef, "content", providers).then(ref => this.componentRef = ref); + dcl.loadIntoLocation(component, elementRef, "content", providers).then(ref => { + this.componentRef = ref; + + // TODO: wire uiCanExit and uiOnParamsChanged callbacks + + // Set resolve data to matching @Input("prop") + let inputs = ng2ComponentInputs(component); + let bindings = viewDecl['bindings'] || {}; + + inputs.map(tuple => ({ prop: tuple.prop, resolve: bindings[tuple.prop] || tuple.resolve })) + .filter(tuple => resolvables[tuple.resolve] !== undefined) + .forEach(tuple => { ref.instance[tuple.prop] = resolvables[tuple.resolve].data }); + }); } }