Skip to content

Commit 5c083bd

Browse files
docs(Resolve): Document array or object option for resolve: property
docs(ng2.resolve): Document how ng2 component uses resolve data feat(ng2.Resolve): implement ng2 provider-style injection. feat(ng2.Resolve): Use root ng2 Injector for resolve system feat(ng2.Resolve): Supply the current `Transition` as a Resolvable
1 parent a7e5ea6 commit 5c083bd

File tree

5 files changed

+136
-48
lines changed

5 files changed

+136
-48
lines changed

src/ng2/interface.ts

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
/** @module ng2 */ /** */
22
import {StateDeclaration, _ViewDeclaration} from "../state/interface";
3-
import {ParamDeclaration} from "../params/interface";
4-
import {IInjectable} from "../common/common";
53
import {Transition} from "../transition/transition";
64
import {Type} from "@angular/core";
75

@@ -142,16 +140,11 @@ export interface Ng2StateDeclaration extends StateDeclaration, Ng2ViewDeclaratio
142140

143141
export interface Ng2ViewDeclaration extends _ViewDeclaration {
144142
/**
145-
* The class of the `Component` to use for this view.
143+
* The `Component` class to use for this view.
146144
*
147145
* A property of [[Ng2StateDeclaration]] or [[Ng2ViewDeclaration]]:
148146
*
149-
* The component class which will be used for this view.
150-
*
151-
* Resolve data can be provided to the component using Dependency Injection. Currently, resolves must be injected
152-
* into the component using `@Inject('key')`, where `key` is the name of the resolve.
153-
*
154-
* TODO: document ng2 shorthand, like ng1's shorthand: inside a "views:" block, a bare string `"foo"` is shorthand for `{ component: "foo" }`
147+
* ### The component class which will be used for this view.
155148
*
156149
* @example
157150
* ```js
@@ -170,8 +163,9 @@ export interface Ng2ViewDeclaration extends _ViewDeclaration {
170163
* }
171164
* }
172165
*
166+
* // Named views shorthand:
167+
* // Inside a "views:" block, a Component class (NavBar) is shorthand for { component: NavBar }
173168
* .state('contacts', {
174-
* // Inside a "views:" block, supplying only a Component class is shorthand for { component: NavBar }
175169
* // use the <nav-bar></nav-bar> component for the view named 'header'
176170
* // use the <contact-list></contact-list> component for the view named 'content'
177171
* views: {
@@ -180,17 +174,62 @@ export interface Ng2ViewDeclaration extends _ViewDeclaration {
180174
* }
181175
* }
182176
* ```
177+
*
178+
* ### Accessing Resolve Data
179+
*
180+
* The component can access the Transition's [[Ng2StateDeclaration.resolve]] data in one of two ways:
181+
*
182+
* 1) Using Dependency Injection in the component constructor
183+
*
184+
* (using Typescript)
185+
* ```js
186+
* class MyComponent {
187+
* constructor(@Inject("myResolveData") public resolveValueA, resolveValueB: public SomeClass) {
188+
* }
189+
* }
190+
* ```
191+
*
192+
* (using ES6/7/babel)
193+
* ```js
194+
* class MyComponent {
195+
* static get parameters() {
196+
* return [["myResolveData"], [MyResolveClass]];
197+
* }
198+
* constructor(resolveValueA, resolveValueB) {
199+
* this.resolveValueA = resolveValueA;
200+
* this.resolveValueB = resolveValueB;
201+
* }
202+
* }
203+
* ```
204+
*
205+
* See also: https://github.com/shuhei/babel-plugin-angular2-annotations
206+
*
207+
* 2) Using a component input
208+
*
209+
* Note: To bind a resolve to a component input, the resolves must `provide:` a string value
210+
*
211+
* ```js
212+
* @Component() {
213+
* inputs: ['resolveValueA']
214+
* }
215+
* class MyComponent {
216+
* myResolveValueA;
217+
* @Input() resolveValueB;
218+
* @Input("resolveValueC") resolveValueC;
219+
*
220+
* constructor() {
221+
* }
222+
* }
223+
* ```
183224
*/
184225
component?: Type;
185226

186227
/**
187-
* @hidden
188-
*
189-
* An object which maps `resolve`s to [[component]] `bindings`.
228+
* An object which maps `resolve` keys to [[component]] `bindings`.
190229
*
191230
* A property of [[Ng2StateDeclaration]] or [[Ng2ViewDeclaration]]:
192231
*
193-
* When using a [[component]] declaration (`component: 'myComponent'`), each input binding for the component is supplied
232+
* When using a [[component]] declaration (`component: MyComponent`), each input binding for the component is supplied
194233
* data from a resolve of the same name, by default. You may supply data from a different resolve name by mapping it here.
195234
*
196235
* Each key in this object is the name of one of the component's input bindings.
@@ -226,10 +265,12 @@ export interface Ng2ViewDeclaration extends _ViewDeclaration {
226265
* ```
227266
*
228267
*/
229-
// bindings?: { [key: string]: string };
268+
bindings?: { [key: string]: string };
230269
}
231270

232271
/**
272+
* @hidden
273+
*
233274
* The shape of a controller for a view (and/or component), defining the controller callbacks.
234275
*
235276
* A view in UI-Router is comprised of either a `component` ([[Ng2ViewDeclaration.component]]) or a combination of a

src/ng2/providers.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
*
4747
* @preferred @module ng2
4848
*/ /** */
49-
import {Provider, provide} from "@angular/core";
49+
import {Injector} from "@angular/core";
5050
import {UIRouter} from "../router";
5151
import {PathNode} from "../path/node";
5252
import {StateRegistry} from "../state/stateRegistry";
@@ -61,8 +61,10 @@ import {Ng2ViewDeclaration} from "./interface";
6161
import {UIRouterConfig} from "./uiRouterConfig";
6262
import {UIRouterGlobals} from "../globals";
6363
import {UIRouterLocation} from "./location";
64+
import {services} from "../common/coreservices";
6465

65-
let uiRouterFactory = (routerConfig: UIRouterConfig, location: UIRouterLocation) => {
66+
let uiRouterFactory = (routerConfig: UIRouterConfig, location: UIRouterLocation, injector: Injector) => {
67+
services.$injector.get = injector.get.bind(injector);
6668
let router = new UIRouter();
6769

6870
location.init();
@@ -95,25 +97,33 @@ let uiRouterFactory = (routerConfig: UIRouterConfig, location: UIRouterLocation)
9597
* ]);
9698
* ```
9799
*/
98-
export const UIROUTER_PROVIDERS: Provider[] = [
99-
provide(UIRouter, { useFactory: uiRouterFactory, deps: [UIRouterConfig, UIRouterLocation] }),
100100

101-
provide(UIRouterLocation, { useClass: UIRouterLocation }),
101+
export const UIROUTER_PROVIDERS: ProviderLike[] = [
102+
{ provide: UIRouter, useFactory: uiRouterFactory, deps: [UIRouterConfig, UIRouterLocation, Injector] },
102103

103-
provide(StateService, { useFactory: (r: UIRouter) => { return r.stateService; }, deps: [UIRouter]}),
104+
{ provide: UIRouterLocation, useClass: UIRouterLocation },
104105

105-
provide(TransitionService, { useFactory: (r: UIRouter) => { return r.transitionService; }, deps: [UIRouter]}),
106+
{ provide: StateService, useFactory: (r: UIRouter) => { return r.stateService; }, deps: [UIRouter]},
106107

107-
provide(UrlMatcherFactory, { useFactory: (r: UIRouter) => { return r.urlMatcherFactory; }, deps: [UIRouter]}),
108+
{ provide: TransitionService, useFactory: (r: UIRouter) => { return r.transitionService; }, deps: [UIRouter]},
108109

109-
provide(UrlRouter, { useFactory: (r: UIRouter) => { return r.urlRouter; }, deps: [UIRouter]}),
110+
{ provide: UrlMatcherFactory, useFactory: (r: UIRouter) => { return r.urlMatcherFactory; }, deps: [UIRouter]},
110111

111-
provide(ViewService, { useFactory: (r: UIRouter) => { return r.viewService; }, deps: [UIRouter]}),
112+
{ provide: UrlRouter, useFactory: (r: UIRouter) => { return r.urlRouter; }, deps: [UIRouter]},
112113

113-
provide(StateRegistry, { useFactory: (r: UIRouter) => { return r.stateRegistry; }, deps: [UIRouter]}),
114+
{ provide: ViewService, useFactory: (r: UIRouter) => { return r.viewService; }, deps: [UIRouter]},
114115

115-
provide(UIRouterGlobals, { useFactory: (r: UIRouter) => { return r.globals; }, deps: [UIRouter]}),
116+
{ provide: StateRegistry, useFactory: (r: UIRouter) => { return r.stateRegistry; }, deps: [UIRouter]},
116117

117-
provide(UiView.PARENT_INJECT, { useFactory: (r: StateRegistry) => { return { fqn: null, context: r.root() } }, deps: [StateRegistry]} )
118+
{ provide: UIRouterGlobals, useFactory: (r: UIRouter) => { return r.globals; }, deps: [UIRouter]},
119+
120+
{ provide: UiView.PARENT_INJECT, useFactory: (r: StateRegistry) => { return { fqn: null, context: r.root() } }, deps: [StateRegistry]}
118121
];
119122

123+
export interface ProviderLike {
124+
provide: any,
125+
useClass?: any,
126+
useFactory?: Function,
127+
deps?: any[]
128+
}
129+

src/state/interface.ts

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -147,26 +147,53 @@ export interface StateDeclaration {
147147
$$state?: () => State;
148148

149149
/**
150-
* Resolve - a mechanism to fetch data, which participates in the transition lifecycle
150+
* Resolve - a mechanism to asynchronously fetch data, while participating in the Transition lifecycle
151151
*
152-
* An object which defines dynamic dependencies/data that can then be injected into this state (or its children)
153-
* during a Transition.
152+
* The `resolve:` property defines data (or other dependencies) to be fetched asynchronously when the state
153+
* is being entered. After the data is fetched, it can be used in views, transition hooks or other resolves
154+
* that belong to this state or any nested states.
155+
*
156+
* ### As an array
157+
*
158+
* Each array element should either be a [[Resolvable]] or an Angular 2 style
159+
* [provider literal](https://angular.io/docs/ts/latest/cookbook/dependency-injection.html#!#provide).
160+
*
161+
* Note:
162+
* ```new Resolvable('token', (http) => http.get('/'), [ Http ])```
163+
* is roughly equivalent to this provider literal:
164+
* ```{ provide: "token", useFactory: (http) => http.get('/'), deps: [ Http ] }```
165+
*
166+
* @example
167+
* ```js
168+
*
169+
* import {Resolvable} from "ui-router-ng2"; // or "angular-ui-router"
170+
* ...
171+
*
172+
* // ng2 example
173+
* resolve: [
174+
* // If you inject `myStateDependency` into a component, you'll get "abc"
175+
* { provide: 'myStateDependency', useFactory: () => 'abc' },
176+
* new Resolvable('myFoos', (http, trans) => http.get(`/foos/${trans.params().fooId}`), [Http, Transition])
177+
* ]
178+
* ```
179+
*
180+
* ### As an object
154181
*
155-
* Define a new dependency by adding a key/value to the `resolve` property of the [[StateDeclaration]].
156182
* - The key (string) is the name of the dependency.
157183
* - The value (function) is an injectable function which returns the dependency, or a promise for the dependency.
158184
*
159185
* @example
160186
* ```js
161187
*
188+
* // ng1 example
162189
* resolve: {
163190
* // If you inject `myStateDependency` into a controller, you'll get "abc"
164191
* myStateDependency: function() {
165192
* return "abc";
166193
* },
167-
* myAsyncData: function($http) {
194+
* myAsyncData: function($http, $transition$) {
168195
* // Return a promise (async) for the data
169-
* return $http.get("/api/v1/data");
196+
* return $http.get("/foos/" + $transition$.params().foo);
170197
* }
171198
* }
172199
* ```
@@ -197,25 +224,33 @@ export interface StateDeclaration {
197224
* Since resolve functions are injected, a common pattern is to inject a custom service such as `UserService`
198225
* and delegate to a custom service method, such as `UserService.list()`;
199226
*
200-
* A resolve function can inject some special values:
227+
* #### Angular 1
228+
*
229+
* An Angular 1 resolve function can inject some special values:
201230
* - `$transition$`: The current [[Transition]] object; information and API about the current transition, such as
202231
* "to" and "from" State Parameters and transition options.
203232
* - Other resolves: This resolve can depend on another resolve, either from the same state, or from any parent state.
204233
* - `$stateParams`: (deprecated) The parameters for the current state (Note: these parameter values are
205234
*
235+
* #### Angular 2
236+
*
237+
* An Angular 2 resolve function can inject some special values:
238+
* - `Transition`: The current [[Transition]] object; information and API about the current transition, such as
239+
* "to" and "from" State Parameters and transition options.
240+
* - Other resolves: This resolve can depend on another resolve, either from the same state, or from any parent state.
241+
*
206242
* @example
207243
* ```js
208244
*
209-
* resolve: {
210-
* // Define a resolve 'allusers' which delegates to the UserService
211-
* allusers: function(UserService) {
212-
* return UserService.list(); // list() returns a promise (async) for all the users
213-
* },
245+
* // Injecting a resolve into another resolve
246+
* resolve: [
247+
* // Define a resolve 'allusers' which delegates to the UserService.list()
248+
* // which returns a promise (async) for all the users
249+
* { provide: 'allusers', useFactory: (UserService) => UserService.list(), deps: [UserService] },
250+
*
214251
* // Define a resolve 'user' which depends on the allusers resolve.
215252
* // This resolve function is not called until 'allusers' is ready.
216-
* user: function(allusers, $transition$) {
217-
* return _.find(allusers, $transition$.params().userId);
218-
* }
253+
* { provide: 'user', (allusers, trans) => _.find(allusers, trans.params().userId, deps: ['allusers', Transition] }
219254
* }
220255
* ```
221256
*/

src/state/stateBuilder.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,15 @@ function includesBuilder(state: State) {
131131
export function resolvablesBuilder(state: State): Resolvable[] {
132132
const obj2Array = obj => Object.keys(obj || {}).map(token => ({token, val: obj[token], deps: undefined}));
133133
const annotate = fn => fn.$inject || services.$injector.annotate(fn, services.$injector.strictDi);
134-
const isLikeNg2Provider = obj => obj.token && (obj.useValue || obj.useFactory || obj.useExisting || obj.useClass);
134+
const isLikeNg2Provider = obj => !!((obj.provide || obj.token) && (obj.useValue || obj.useFactory || obj.useExisting || obj.useClass));
135+
const provideToken = p => p.provide || p.token;
135136

136137
/** Given a provider, returns a Resolvable */
137138
const provider2Resolvable = pattern([
138-
[prop('useFactory'), p => new Resolvable(p.token, p.useFactory, p.dependencies)],
139-
[prop('useClass'), p => new Resolvable(p.token, () => new (<any>p.useClass)(), [])],
140-
[prop('useValue'), p => new Resolvable(p.token, () => p.useValue, [], p.useValue)],
141-
[prop('useExisting'), p => new Resolvable(p.token, (x) => x, [p.useExisting])],
139+
[prop('useFactory'), p => new Resolvable(provideToken(p), p.useFactory, (p.deps || p.dependencies))],
140+
[prop('useClass'), p => new Resolvable(provideToken(p), () => new (<any>p.useClass)(), [])],
141+
[prop('useValue'), p => new Resolvable(provideToken(p), () => p.useValue, [], p.useValue)],
142+
[prop('useExisting'), p => new Resolvable(provideToken(p), (x) => x, [p.useExisting])],
142143
]);
143144

144145
const item2Resolvable = <(any) => Resolvable> pattern([

src/transition/transition.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export class Transition implements IHookRegistry {
154154
PathFactory.bindResolveContexts(this._treeChanges.to);
155155

156156
let rootResolvables: Resolvable[] = [
157+
new Resolvable(Transition, () => this, [], this),
157158
new Resolvable('$transition$', () => this, [], this),
158159
new Resolvable('$stateParams', () => this.params(), [], this.params())
159160
];

0 commit comments

Comments
 (0)