1- /** @module view */ /** for typedoc */
1+ /** @module view */
2+ /** for typedoc */
23import { ng as angular } from "./angular" ;
4+ import { IAugmentedJQuery } from "angular" ;
35import {
4- isArray , isDefined , isFunction , isObject , services , Obj , IInjectable , tail , kebobString , unnestR , ResolveContext , Resolvable , RawParams
6+ isArray , isDefined , isFunction , isObject , services , Obj , IInjectable , tail , kebobString , unnestR , ResolveContext ,
7+ Resolvable , RawParams , identity
58} from "ui-router-core" ;
69import { Ng1ViewDeclaration } from "./interface" ;
710
@@ -25,13 +28,16 @@ export class TemplateFactory {
2528 fromConfig ( config : Ng1ViewDeclaration , params : any , context : ResolveContext ) {
2629 const defaultTemplate = "<ui-view></ui-view>" ;
2730
31+ const asTemplate = ( result ) => services . $q . when ( result ) . then ( str => ( { template : str } ) ) ;
32+ const asComponent = ( result ) => services . $q . when ( result ) . then ( str => ( { component : str } ) ) ;
33+
2834 return (
29- isDefined ( config . template ) ? this . fromString ( config . template , params ) :
30- isDefined ( config . templateUrl ) ? this . fromUrl ( config . templateUrl , params ) :
31- isDefined ( config . templateProvider ) ? this . fromProvider ( config . templateProvider , params , context ) :
32- isDefined ( config . component ) ? this . fromComponent ( config . component , config . bindings ) :
33- isDefined ( config . componentProvider ) ? this . fromComponentProvider ( config . componentProvider , params , context ) :
34- defaultTemplate
35+ isDefined ( config . template ) ? asTemplate ( this . fromString ( config . template , params ) ) :
36+ isDefined ( config . templateUrl ) ? asTemplate ( this . fromUrl ( config . templateUrl , params ) ) :
37+ isDefined ( config . templateProvider ) ? asTemplate ( this . fromProvider ( config . templateProvider , params , context ) ) :
38+ isDefined ( config . component ) ? asComponent ( config . component ) :
39+ isDefined ( config . componentProvider ) ? asComponent ( this . fromComponentProvider ( config . componentProvider , params , context ) ) :
40+ asTemplate ( defaultTemplate )
3541 ) ;
3642 } ;
3743
@@ -78,50 +84,73 @@ export class TemplateFactory {
7884 return resolvable . get ( context ) ;
7985 } ;
8086
87+ /**
88+ * Creates a component's template by invoking an injectable provider function.
89+ *
90+ * @param provider Function to invoke via `locals`
91+ * @param {Function } injectFn a function used to invoke the template provider
92+ * @return {string } The template html as a string: "<component-name input1='::$resolve.foo'></component-name>".
93+ */
94+ fromComponentProvider ( provider : IInjectable , params : any , context : ResolveContext ) {
95+ let deps = services . $injector . annotate ( provider ) ;
96+ let providerFn = isArray ( provider ) ? tail ( < any [ ] > provider ) : provider ;
97+ let resolvable = new Resolvable ( "" , < Function > providerFn , deps ) ;
98+ return resolvable . get ( context ) ;
99+ } ;
100+
81101 /**
82102 * Creates a template from a component's name
83103 *
104+ * @param uiView {object} The parent ui-view (for binding outputs to callbacks)
105+ * @param context The ResolveContext (for binding outputs to callbacks returned from resolves)
84106 * @param component {string} Component's name in camel case.
85107 * @param bindings An object defining the component's bindings: {foo: '<'}
86108 * @return {string } The template as a string: "<component-name input1='::$resolve.foo'></component-name>".
87109 */
88- fromComponent ( component : string , bindings ?: any ) {
89- const resolveFor = ( key : string ) =>
90- bindings && bindings [ key ] || key ;
110+ makeComponentTemplate ( uiView : IAugmentedJQuery , context : ResolveContext , component : string , bindings ?: any ) {
111+ bindings = bindings || { } ;
112+
91113 // Bind once prefix
92114 const prefix = angular . version . minor >= 3 ? "::" : "" ;
115+
93116 const attributeTpl = ( input : BindingTuple ) => {
94- var attrName = kebobString ( input . name ) ;
95- var resolveName = resolveFor ( input . name ) ;
96- if ( input . type === '@' )
117+ let { name, type } = input ;
118+ let attrName = kebobString ( name ) ;
119+ // If the ui-view has an attribute which matches a binding on the routed component
120+ // then pass that attribute through to the routed component template.
121+ // Prefer ui-view wired mappings to resolve data, unless the resolve was explicitly bound using `bindings:`
122+ if ( uiView . attr ( attrName ) && ! bindings [ name ] )
123+ return `${ attrName } ='${ uiView . attr ( attrName ) } '` ;
124+
125+ let resolveName = bindings [ name ] || name ;
126+ // Pre-evaluate the expression for "@" bindings by enclosing in {{ }}
127+ // some-attr="{{ ::$resolve.someResolveName }}"
128+ if ( type === '@' )
97129 return `${ attrName } ='{{${ prefix } $resolve.${ resolveName } }}'` ;
130+
131+ // Wire "&" callbacks to resolves that return a callback function
132+ // Get the result of the resolve (should be a function) and annotate it to get its arguments.
133+ // some-attr="$resolve.someResolveResultName(foo, bar)"
134+ if ( type === '&' ) {
135+ let res = context . getResolvable ( resolveName ) ;
136+ let fn = res && res . data ;
137+ let args = fn && services . $injector . annotate ( fn ) || [ ] ;
138+ let arrayIdxStr = isArray ( fn ) ? `[${ fn . length - 1 } ]` : '' ;
139+ return `${ attrName } ='$resolve.${ resolveName } ${ arrayIdxStr } (${ args . join ( "," ) } )'` ;
140+ }
141+
142+ // some-attr="::$resolve.someResolveName"
98143 return `${ attrName } ='${ prefix } $resolve.${ resolveName } '` ;
99144 } ;
100145
101- let attrs = getComponentInputs ( component ) . map ( attributeTpl ) . join ( " " ) ;
146+ let attrs = getComponentBindings ( component ) . map ( attributeTpl ) . join ( " " ) ;
102147 let kebobName = kebobString ( component ) ;
103148 return `<${ kebobName } ${ attrs } ></${ kebobName } >` ;
104149 } ;
105-
106- /**
107- * Creates a component's template by invoking an injectable provider function.
108- *
109- * @param provider Function to invoke via `locals`
110- * @param {Function } injectFn a function used to invoke the template provider
111- * @return {string } The template html as a string: "<component-name input1='::$resolve.foo'></component-name>".
112- */
113- fromComponentProvider ( provider : IInjectable , params : any , context : ResolveContext ) {
114- let deps = services . $injector . annotate ( provider ) ;
115- let providerFn = isArray ( provider ) ? tail ( < any [ ] > provider ) : provider ;
116- let resolvable = new Resolvable ( "" , < Function > providerFn , deps ) ;
117- return resolvable . get ( context ) . then ( ( componentName ) => {
118- return this . fromComponent ( componentName ) ;
119- } ) ;
120- } ;
121150}
122151
123- // Gets all the directive(s)' inputs ('@', '=', and '<')
124- function getComponentInputs ( name : string ) {
152+ // Gets all the directive(s)' inputs ('@', '=', and '<') and outputs ('&')
153+ function getComponentBindings ( name : string ) {
125154 let cmpDefs = < any [ ] > services . $injector . get ( name + "Directive" ) ; // could be multiple
126155 if ( ! cmpDefs || ! cmpDefs . length ) throw new Error ( `Unable to find component named '${ name } '` ) ;
127156 return cmpDefs . map ( getBindings ) . reduce ( unnestR , [ ] ) ;
@@ -143,7 +172,7 @@ interface BindingTuple {
143172// for ng 1.3 through ng 1.5, process the component's bindToController: { input: "=foo" } object
144173const scopeBindings = ( bindingsObj : Obj ) => Object . keys ( bindingsObj || { } )
145174 // [ 'input', [ '=foo', '=', 'foo' ] ]
146- . map ( key => [ key , / ^ ( [ = < @ ] ) [ ? ] ? ( .* ) / . exec ( bindingsObj [ key ] ) ] )
175+ . map ( key => [ key , / ^ ( [ = < @ & ] ) [ ? ] ? ( .* ) / . exec ( bindingsObj [ key ] ) ] )
147176 // skip malformed values
148177 . filter ( tuple => isDefined ( tuple ) && isArray ( tuple [ 1 ] ) )
149178 // { name: ('foo' || 'input'), type: '=' }
0 commit comments