import { ParamDeclaration, RawParams } from '../params/interface'; import { StateObject } from './stateObject'; import { ViewContext } from '../view/interface'; import { IInjectable } from '../common/common'; import { Transition } from '../transition/transition'; import { TransitionStateHookFn, TransitionOptions } from '../transition/interface'; import { ResolvePolicy, ResolvableLiteral, ProviderLike } from '../resolve/interface'; import { Resolvable } from '../resolve/resolvable'; import { TargetState } from './targetState'; export declare type StateOrName = string | StateDeclaration | StateObject; export interface TransitionPromise extends Promise { transition: Transition; } export interface TargetStateDef { state: StateOrName; params?: RawParams; options?: TransitionOptions; } export declare type ResolveTypes = Resolvable | ResolvableLiteral | ProviderLike; /** * Base interface for declaring a view * * This interface defines the basic data that a normalized view declaration will have on it. * Each implementation of UI-Router (for a specific framework) should define its own extension of this interface. * Add any additional fields that the framework requires to that interface. */ export interface _ViewDeclaration { /** * The raw name for the view declaration, i.e., the [[StateDeclaration.views]] property name. */ $name?: string; /** * The normalized address for the `ui-view` which this ViewConfig targets. * * A ViewConfig targets a `ui-view` in the DOM (relative to the `uiViewContextAnchor`) which has * a specific name. * @example `header` or `$default` * * The `uiViewName` can also target a _nested view_ by providing a dot-notation address * @example `foo.bar` or `foo.$default.bar` */ $uiViewName?: string; /** * The normalized context anchor (state name) for the `uiViewName` * * When targeting a `ui-view`, the `uiViewName` address is anchored to a context name (state name). */ $uiViewContextAnchor?: string; /** * A type identifier for the View * * This is used when loading prerequisites for the view, before it enters the DOM. Different types of views * may load differently (e.g., templateProvider+controllerProvider vs component class) */ $type?: string; /** * The context that this view is declared within. */ $context?: ViewContext; } /** * The return value of a [[redirectTo]] function * * - string: a state name * - TargetState: a target state, parameters, and options * - object: an object with a state name and parameters */ export declare type RedirectToResult = string | TargetState | { state?: string; params?: RawParams; } | void; /** * The StateDeclaration object is used to define a state or nested state. * * Note: Each implementation of UI-Router (for a specific framework) * extends this interface as necessary. * * #### Example: * ```js * // StateDeclaration object * var foldersState = { * name: 'folders', * url: '/folders', * component: FoldersComponent, * resolve: { * allfolders: function(FolderService) { * return FolderService.list(); * } * }, * } * * registry.register(foldersState); * ``` */ export interface StateDeclaration { /** * The state name (required) * * A unique state name, e.g. `"home"`, `"about"`, `"contacts"`. * To create a parent/child state use a dot, e.g. `"about.sales"`, `"home.newest"`. * * Note: [State] objects require unique names. * The name is used like an id. */ name?: string; /** * Abstract state indicator * * An abstract state can never be directly activated. * Use an abstract state to provide inherited properties (url, resolve, data, etc) to children states. */ abstract?: boolean; /** * The parent state * * Normally, a state's parent is implied from the state's [[name]], e.g., `"parentstate.childstate"`. * * Alternatively, you can explicitly set the parent state using this property. * This allows shorter state names, e.g., `Child` * instead of `Child * * When using this property, the state's name should not have any dots in it. * * #### Example: * ```js * var parentstate = { * name: 'parentstate' * } * var childstate = { * name: 'childstate', * parent: 'parentstate' * // or use a JS var which is the parent StateDeclaration, i.e.: * // parent: parentstate * } * ``` */ parent?: string | StateDeclaration; /** * Gets the internal State object API * * Gets the *internal API* for a registered state. * * Note: the internal [[StateObject]] API is subject to change without notice * @internal */ $$state?: () => StateObject; /** * Resolve - a mechanism to asynchronously fetch data, participating in the Transition lifecycle * * The `resolve:` property defines data (or other dependencies) to be fetched asynchronously when the state is being entered. * After the data is fetched, it may be used in views, transition hooks or other resolves that belong to this state. * The data may also be used in any views or resolves that belong to nested states. * * ### As an array * * Each array element should be a [[ResolvableLiteral]] object. * * #### Example: * The `user` resolve injects the current `Transition` and the `UserService` (using its token, which is a string). * The [[ResolvableLiteral.resolvePolicy]] sets how the resolve is processed. * The `user` data, fetched asynchronously, can then be used in a view. * ```js * var state = { * name: 'user', * url: '/user/:userId * resolve: [ * { * token: 'user', * policy: { when: 'EAGER' }, * deps: ['UserService', Transition], * resolveFn: (userSvc, trans) => userSvc.fetchUser(trans.params().userId) }, * } * ] * } * ``` * * Note: an Angular 2 style [`useFactory` provider literal](https://angular.io/docs/ts/latest/cookbook/dependency-injection.html#!#provide) * may also be used. See [[ProviderLike]]. * #### Example: * ``` * resolve: [ * { provide: 'token', useFactory: (http) => http.get('/'), deps: [ Http ] }, * ] * ``` * * ### As an object * * The `resolve` property may be an object where: * - Each key (string) is the name of the dependency. * - Each value (function) is an injectable function which returns the dependency, or a promise for the dependency. * * This style is based on AngularJS injectable functions, but can be used with any UI-Router implementation. * If your code will be minified, the function should be ["annotated" in the AngularJS manner](https://docs.angularjs.org/guide/di#dependency-annotation). * * #### AngularJS Example: * ```js * resolve: { * // If you inject `myStateDependency` into a controller, you'll get "abc" * myStateDependency: function() { * return "abc"; * }, * // Dependencies are annotated in "Inline Array Annotation" * myAsyncData: ['$http', '$transition$' function($http, $transition$) { * // Return a promise (async) for the data * return $http.get("/foos/" + $transition$.params().foo); * }] * } * ``` * * Note: You cannot specify a policy for each Resolvable, nor can you use non-string * tokens when using the object style `resolve:` block. * * ### Lifecycle * * Since a resolve function can return a promise, the router will delay entering the state until the promises are ready. * If any of the promises are rejected, the Transition is aborted with an Error. * * By default, resolves for a state are fetched just before that state is entered. * Note that only states which are being *entered* during the `Transition` have their resolves fetched. * States that are "retained" do not have their resolves re-fetched. * * If you are currently in a parent state `parent` and are transitioning to a child state `parent.child`, the * previously resolved data for state `parent` can be injected into `parent.child` without delay. * * Any resolved data for `parent.child` is retained until `parent.child` is exited, e.g., by transitioning back to the `parent` state. * * Because of this scoping and lifecycle, resolves are a great place to fetch your application's primary data. * * ### Injecting resolves into other things * * During a transition, Resolve data can be injected into: * * - Views (the components which fill a `ui-view` tag) * - Transition Hooks * - Other resolves (a resolve may depend on asynchronous data from a different resolve) * * ### Injecting other things into resolves * * Resolve functions usually have dependencies on some other API(s). * The dependencies are usually declared and injected into the resolve function. * A common pattern is to inject a custom service such as `UserService`. * The resolve then delegates to a service method, such as `UserService.list()`; * * #### Special injectable tokens * * - `UIRouter`: The [[UIRouter]] instance which has references to all the UI-Router services. * - `Transition`: The current [[Transition]] object; information and API about the current transition, such as * "to" and "from" State Parameters and transition options. * - `'$transition$'`: A string alias for the `Transition` injectable * - `'$state$'`: For `onEnter`/`onExit`/`onRetain`, the state being entered/exited/retained. * - Other resolve tokens: A resolve can depend on another resolve, either from the same state, or from any parent state. * * #### Example: * ```js * // Injecting a resolve into another resolve * resolve: [ * // Define a resolve 'allusers' which delegates to the UserService.list() * // which returns a promise (async) for all the users * { provide: 'allusers', useFactory: (UserService) => UserService.list(), deps: [UserService] }, * * // Define a resolve 'user' which depends on the allusers resolve. * // This resolve function is not called until 'allusers' is ready. * { provide: 'user', (allusers, trans) => _.find(allusers, trans.params().userId, deps: ['allusers', Transition] } * } * ``` */ resolve?: ResolveTypes[] | { [key: string]: IInjectable; }; /** * Sets the resolve policy defaults for all resolves on this state * * This should be an [[ResolvePolicy]] object. * * It can contain the following optional keys/values: * * - `when`: (optional) defines when the resolve is fetched. Accepted values: "LAZY" or "EAGER" * - `async`: (optional) if the transition waits for the resolve. Accepted values: "WAIT", "NOWAIT", {@link CustomAsyncPolicy} * * See [[ResolvePolicy]] for more details. */ resolvePolicy?: ResolvePolicy; /** * The url fragment for the state * * A URL fragment (with optional parameters) which is used to match the browser location with this state. * * This fragment will be appended to the parent state's URL in order to build up the overall URL for this state. * See [[UrlMatcher]] for details on acceptable patterns. * * @examples * ```js * * url: "/home" * // Define a parameter named 'userid' * url: "/users/:userid" * // param 'bookid' has a custom regexp * url: "/books/{bookid:[a-zA-Z_-]}" * // param 'categoryid' is of type 'int' * url: "/books/{categoryid:int}" * // two parameters for this state * url: "/books/{publishername:string}/{categoryid:int}" * // Query parameters * url: "/messages?before&after" * // Query parameters of type 'date' * url: "/messages?{before:date}&{after:date}" * // Path and query parameters * url: "/messages/:mailboxid?{before:date}&{after:date}" * ``` */ url?: string; /** * Params configuration * * An object which optionally configures parameters declared in the url, or defines additional non-url * parameters. For each parameter being configured, add a [[ParamDeclaration]] keyed to the name of the parameter. * * #### Example: * ```js * params: { * param1: { * type: "int", * array: true, * value: [] * }, * param2: { * value: "index" * } * } * ``` */ params?: { [key: string]: ParamDeclaration | any; }; /** * Named views * * An optional object which defines multiple views, or explicitly targets specific named ui-views. * * - What is a view config * - What is a ui-view * - Shorthand controller/template * - Incompatible with ^ * * Examples: * * Targets three named ui-views in the parent state's template * * #### Example: * ```js * views: { * header: { * controller: "headerCtrl", * templateUrl: "header.html" * }, body: { * controller: "bodyCtrl", * templateUrl: "body.html" * }, footer: { * controller: "footCtrl", * templateUrl: "footer.html" * } * } * ``` * * @example * ```js * // Targets named ui-view="header" from ancestor state 'top''s template, and * // named `ui-view="body" from parent state's template. * views: { * 'header@top': { * controller: "msgHeaderCtrl", * templateUrl: "msgHeader.html" * }, 'body': { * controller: "messagesCtrl", * templateUrl: "messages.html" * } * } * ``` */ views?: { [key: string]: _ViewDeclaration; }; /** * An inherited property to store state data * * This is a spot for you to store inherited state metadata. * Child states' `data` object will prototypally inherit from their parent state. * * This is a good spot to put metadata such as `requiresAuth`. * * Note: because prototypal inheritance is used, changes to parent `data` objects reflect in the child `data` objects. * Care should be taken if you are using `hasOwnProperty` on the `data` object. * Properties from parent objects will return false for `hasOwnProperty`. */ data?: any; /** * Synchronously or asynchronously redirects Transitions to a different state/params * * If this property is defined, a Transition directly to this state will be redirected based on the property's value. * * - If the value is a `string`, the Transition is redirected to the state named by the string. * * - If the property is an object with a `state` and/or `params` property, * the Transition is redirected to the named `state` and/or `params`. * * - If the value is a [[TargetState]] the Transition is redirected to the `TargetState` * * - If the property is a function: * - The function is called with the current [[Transition]] * - The return value is processed using the previously mentioned rules. * - If the return value is a promise, the promise is waited for, then the resolved async value is processed using the same rules. * * Note: `redirectTo` is processed as an `onStart` hook, before `LAZY` resolves. * If your redirect function relies on resolve data, get the [[Transition.injector]] and get a * promise for the resolve data using [[UIInjector.getAsync]]. * * #### Example: * ```js * // a string * .state('A', { * redirectTo: 'A.B' * }) * * // a {state, params} object * .state('C', { * redirectTo: { state: 'C.D', params: { foo: 'index' } } * }) * * // a fn * .state('E', { * redirectTo: () => "A" * }) * * // a fn conditionally returning a {state, params} * .state('F', { * redirectTo: (trans) => { * if (trans.params().foo < 10) * return { state: 'F', params: { foo: 10 } }; * } * }) * * // a fn returning a promise for a redirect * .state('G', { * redirectTo: (trans) => { * let svc = trans.injector().get('SomeAsyncService') * let promise = svc.getAsyncRedirectTo(trans.params.foo); * return promise; * } * }) * * // a fn that fetches resolve data * .state('G', { * redirectTo: (trans) => { * // getAsync tells the resolve to load * let resolvePromise = trans.injector().getAsync('SomeResolve') * return resolvePromise.then(resolveData => resolveData === 'login' ? 'login' : null); * } * }) * ``` */ redirectTo?: RedirectToResult | ((transition: Transition) => RedirectToResult) | ((transition: Transition) => Promise); /** * A Transition Hook called with the state is being entered. See: [[IHookRegistry.onEnter]] * * #### Example: * ```js * .state({ * name: 'mystate', * onEnter: function(trans, state) { * console.log("Entering " + state.name); * } * }); * ``` * * Note: The above `onEnter` on the state declaration is effectively sugar for: * * ```js * transitionService.onEnter({ entering: 'mystate' }, function(trans, state) { * console.log("Entering " + state.name); * }); * ``` */ onEnter?: TransitionStateHookFn; /** * A [[TransitionStateHookFn]] called with the state is being retained/kept. See: [[IHookRegistry.onRetain]] * * #### Example: * ```js * .state({ * name: 'mystate', * onRetain: function(trans, state) { * console.log(state.name + " is still active!"); * } * }); * ``` * * Note: The above `onRetain` on the state declaration is effectively sugar for: * * ```js * transitionService.onRetain({ retained: 'mystate' }, function(trans, state) { * console.log(state.name + " is still active!"); * }); * ``` */ onRetain?: TransitionStateHookFn; /** * A Transition Hook called with the state is being exited. See: [[IHookRegistry.onExit]] * * #### Example: * ```js * .state({ * name: 'mystate', * onExit: function(trans, state) { * console.log("Leaving " + state.name); * } * }); * ``` * * Note: The above `onRetain` on the state declaration is effectively sugar for: * * ```js * transitionService.onExit({ exiting: 'mystate' }, function(trans, state) { * console.log("Leaving " + state.name); * }); * ``` */ onExit?: TransitionStateHookFn; /** * A function used to lazy load code * * The `lazyLoad` function is invoked before the state is activated. * The transition waits while the code is loading. * * The function should load the code that is required to activate the state. * For example, it may load a component class, or some service code. * The function must return a promise which resolves when loading is complete. * * For example, this code lazy loads a service before the `abc` state is activated: * * ``` * .state('abc', { * lazyLoad: (transition, state) => import('./abcService') * } * ``` * * The `abcService` file is imported and loaded * (it is assumed that the `abcService` file knows how to register itself as a service). * * #### Lifecycle * * - The `lazyLoad` function is invoked if a transition is going to enter the state. * - The function is invoked before the transition starts (using an `onBefore` transition hook). * - The function is only invoked once; while the `lazyLoad` function is loading code, it will not be invoked again. * For example, if the user double clicks a ui-sref, `lazyLoad` is only invoked once even though there were two transition attempts. * Instead, the existing lazy load promise is re-used. * - When the promise resolves successfully, the `lazyLoad` property is deleted from the state declaration. * - If the promise resolves to a [[LazyLoadResult]] which has an array of `states`, those states are registered. * - The original transition is retried (this time without the `lazyLoad` property present). * * - If the `lazyLoad` function fails, then the transition also fails. * The failed transition (and the `lazyLoad` function) could potentially be retried by the user. * * ### Lazy loading state definitions (Future States) * * State definitions can also be lazy loaded. * This might be desirable when building large, multi-module applications. * * To lazy load state definitions, a Future State should be registered as a placeholder. * When the state definitions are lazy loaded, the Future State is deregistered. * * A future state can act as a placeholder for a single state, or for an entire module of states and substates. * A future state should have: * * - A `name` which ends in `.**`. * A future state's `name` property acts as a wildcard [[Glob]]. * It matches any state name that starts with the `name` (including child states that are not yet loaded). * - A `url` prefix. * A future state's `url` property acts as a wildcard. * UI-Router matches all paths that begin with the `url`. * It effectively appends `.*` to the internal regular expression. * When the prefix matches, the future state will begin loading. * - A `lazyLoad` function. * This function should should return a Promise to lazy load the code for one or more [[StateDeclaration]] objects. * It should return a [[LazyLoadResult]]. * Generally, one of the lazy loaded states should have the same name as the future state. * The new state will then **replace the future state placeholder** in the registry. * * ### Additional resources * * For in depth information on lazy loading and Future States, see the [Lazy Loading Guide](https://ui-router.github.io/guides/lazyload). * * #### Example: states.js * ```js * * // This child state is a lazy loaded future state * // The `lazyLoad` function loads the final state definition * { * name: 'parent.**', * url: '/parent', * lazyLoad: () => import('./lazy.states.js') * } * ``` * * #### Example: lazy.states.js * * This file is lazy loaded. It exports an array of states. * * ```js * import {ChildComponent} from "./child.component.js"; * import {ParentComponent} from "./parent.component.js"; * * // This fully defined state replaces the future state * let parentState = { * // the name should match the future state * name: 'parent', * url: '/parent/:parentId', * component: ParentComponent, * resolve: { * parentData: ($transition$, ParentService) => * ParentService.get($transition$.params().parentId) * } * } * * let childState = { * name: 'parent.child', * url: '/child/:childId', * params: { * childId: "default" * }, * resolve: { * childData: ($transition$, ChildService) => * ChildService.get($transition$.params().childId) * } * }; * * // This array of states will be registered by the lazyLoad hook * let lazyLoadResults = { * states: [ parentState, childState ] * }; * * export default lazyLoadResults; * ``` * * @param transition the [[Transition]] that is activating the future state * @param state the [[StateDeclaration]] that the `lazyLoad` function is declared on * @return a Promise to load the states. * Optionally, if the promise resolves to a [[LazyLoadResult]], * the states will be registered with the [[StateRegistry]]. */ lazyLoad?: (transition: Transition, state: StateDeclaration) => Promise; /** * Marks all the state's parameters as `dynamic`. * * All parameters on the state will use this value for `dynamic` as a default. * Individual parameters may override this default using [[ParamDeclaration.dynamic]] in the [[params]] block. * * Note: this value overrides the `dynamic` value on a custom parameter type ([[ParamTypeDefinition.dynamic]]). */ dynamic?: boolean; /** * Marks all query parameters as [[ParamDeclaration.dynamic]] * * @deprecated use either [[dynamic]] or [[ParamDeclaration.dynamic]] */ reloadOnSearch?: boolean; } /** * The return type of a [[StateDeclaration.lazyLoad]] function * * If your state has a `lazyLoad` function, it should return a promise. * If promise resolves to an object matching this interface, then the `states` array * of [[StateDeclaration]] objects will be automatically registered. */ export interface LazyLoadResult { states?: StateDeclaration[]; } /** * An options object for [[StateService.href]] */ export interface HrefOptions { /** * Defines what state to be "relative from" * * When a relative path is found (e.g `^` or `.bar`), defines which state to be relative from. */ relative?: StateOrName; /** * If true, and if there is no url associated with the state provided in the * first parameter, then the constructed href url will be built from the first * ancestor which has a url. */ lossy?: boolean; /** * If `true` will inherit parameters from the current parameter values. */ inherit?: boolean; /** * If true will generate an absolute url, e.g. `http://www.example.com/fullurl`. */ absolute?: boolean; } /** * Either a [[StateDeclaration]] or an ES6 class that implements [[StateDeclaration]] * The ES6 class constructor should have no arguments. */ export declare type _StateDeclaration = StateDeclaration | { new (): StateDeclaration; };