Skip to content

Commit

Permalink
feat(State): Support registration of ES6 state classes (as opposed to…
Browse files Browse the repository at this point in the history
… object literals)
  • Loading branch information
christopherthielen committed Feb 23, 2017
1 parent cef2a73 commit 3a5d055
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 35 deletions.
43 changes: 36 additions & 7 deletions src/state/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ export 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.
* Framework-specific implementations should add additional fields (to their interfaces which extend this interface).
* 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.
*
* @internalapi
*/
Expand Down Expand Up @@ -90,25 +91,48 @@ export type RedirectToResult = string | TargetState | { state?: string, params?:
/**
* 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();
* }
* },
* template: "<ul><li ng-repeat='folder in allfolders'>{{folder.name}}</li></ul>",
* controller: function(allfolders, $scope) {
* $scope.allfolders = allfolders;
* }
* }
*
* registry.register(foldersState);
* ```
*
* Note: Each front-end framework extends this interface as necessary
* A state declaration may also be an ES6 class which implements the StateDeclaration interface
* and has a `@State()` decorator
*
* #### Example:
* ```js
* import { State } from "ui-router-core"
* import { FolderService } from "../folder.service"
* // StateDeclaration class
* @State()
* export class FoldersState implements StateDeclaration {
* name: 'folders',
* url: '/folders',
* component: FoldersComponent
*
* @Resolve({ deps: [ FolderService ] })
* allfolders(FolderService) {
* return FolderService.list();
* },
* }
*
* registry.register(FoldersState);
* ```
*/
export interface StateDeclaration {
/**
Expand Down Expand Up @@ -161,7 +185,7 @@ export interface StateDeclaration {
*
* Gets the *internal API* for a registered state.
*
* Note: the internal [[State]] API is subject to change without notice
* Note: the internal [[StateObject]] API is subject to change without notice
*/
$$state?: () => StateObject;

Expand Down Expand Up @@ -685,3 +709,8 @@ export interface HrefOptions {
absolute?: boolean;
}

/**
* Either a [[StateDeclaration]] or an ES6 class that implements [[StateDeclaration]]
* The ES6 class constructor should have no arguments.
*/
export type _StateDeclaration = StateDeclaration | { new (): StateDeclaration };
12 changes: 6 additions & 6 deletions src/state/stateBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ function includesBuilder(state: StateObject) {
/**
* This is a [[StateBuilder.builder]] function for the `resolve:` block on a [[StateDeclaration]].
*
* When the [[StateBuilder]] builds a [[State]] object from a raw [[StateDeclaration]], this builder
* When the [[StateBuilder]] builds a [[StateObject]] object from a raw [[StateDeclaration]], this builder
* validates the `resolve` property and converts it to a [[Resolvable]] array.
*
* resolve: input value can be:
Expand Down Expand Up @@ -204,13 +204,13 @@ export function resolvablesBuilder(state: StateObject): Resolvable[] {
/**
* @internalapi A internal global service
*
* StateBuilder is a factory for the internal [[State]] objects.
* StateBuilder is a factory for the internal [[StateObject]] objects.
*
* When you register a state with the [[StateRegistry]], you register a plain old javascript object which
* conforms to the [[StateDeclaration]] interface. This factory takes that object and builds the corresponding
* [[State]] object, which has an API and is used internally.
* [[StateObject]] object, which has an API and is used internally.
*
* Custom properties or API may be added to the internal [[State]] object by registering a decorator function
* Custom properties or API may be added to the internal [[StateObject]] object by registering a decorator function
* using the [[builder]] method.
*/
export class StateBuilder {
Expand Down Expand Up @@ -250,10 +250,10 @@ export class StateBuilder {
}

/**
* Registers a [[BuilderFunction]] for a specific [[State]] property (e.g., `parent`, `url`, or `path`).
* Registers a [[BuilderFunction]] for a specific [[StateObject]] property (e.g., `parent`, `url`, or `path`).
* More than one BuilderFunction can be registered for a given property.
*
* The BuilderFunction(s) will be used to define the property on any subsequently built [[State]] objects.
* The BuilderFunction(s) will be used to define the property on any subsequently built [[StateObject]] objects.
*
* @param name The name of the State property being registered for.
* @param fn The BuilderFunction which will be used to build the State property
Expand Down
24 changes: 15 additions & 9 deletions src/state/stateObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @module state
*/
/** for typedoc */
import { StateDeclaration, _ViewDeclaration } from "./interface";
import { StateDeclaration, _ViewDeclaration, _StateDeclaration } from "./interface";
import { defaults, values, find, inherit } from "../common/common";
import { propEq } from "../common/hof";
import { Param } from "../params/param";
Expand All @@ -13,20 +13,20 @@ import { TransitionStateHookFn } from "../transition/interface";
import { TargetState } from "./targetState";
import { Transition } from "../transition/transition";
import { Glob } from "../common/glob";
import { isObject } from "../common/predicates";
import { isObject, isFunction } from "../common/predicates";

/**
* Internal representation of a UI-Router state.
*
* Instances of this class are created when a [[StateDeclaration]] is registered with the [[StateRegistry]].
*
* A registered [[StateDeclaration]] is augmented with a getter ([[StateDeclaration.$$state]]) which returns the corresponding [[State]] object.
* A registered [[StateDeclaration]] is augmented with a getter ([[StateDeclaration.$$state]]) which returns the corresponding [[StateObject]] object.
*
* This class prototypally inherits from the corresponding [[StateDeclaration]].
* Each of its own properties (i.e., `hasOwnProperty`) are built using builders from the [[StateBuilder]].
*/
export class StateObject {
/** The parent [[State]] */
/** The parent [[StateObject]] */
public parent: StateObject;

/** The name used to register the state */
Expand Down Expand Up @@ -58,15 +58,15 @@ export class StateObject {
public views: { [key: string]: _ViewDeclaration; };

/**
* The original [[StateDeclaration]] used to build this [[State]].
* The original [[StateDeclaration]] used to build this [[StateObject]].
* Note: `this` object also prototypally inherits from the `self` declaration object.
*/
public self: StateDeclaration;

/** The nearest parent [[State]] which has a URL */
/** The nearest parent [[StateObject]] which has a URL */
public navigable: StateObject;

/** The parent [[State]] objects from this state up to the root */
/** The parent [[StateObject]] objects from this state up to the root */
public path: StateObject[];

/**
Expand Down Expand Up @@ -117,7 +117,9 @@ export class StateObject {
* @param stateDecl the user-supplied State Declaration
* @returns {StateObject} an internal State object
*/
static create(stateDecl: StateDeclaration): StateObject {
static create(stateDecl: _StateDeclaration): StateObject {
stateDecl = StateObject.isStateClass(stateDecl) ? new stateDecl() : stateDecl;

let state = inherit(inherit(stateDecl, StateObject.prototype)) as StateObject;
stateDecl.$$state = () => state;
state.self = stateDecl;
Expand All @@ -127,7 +129,11 @@ export class StateObject {
return state;
}

/** Predicate which returns true if the object is an internal [[State]] object */
/** Predicate which returns true if the object is an class with @State() decorator */
static isStateClass = (stateDecl: _StateDeclaration): stateDecl is ({ new (): StateDeclaration }) =>
isFunction(stateDecl) && stateDecl['__uiRouterState'] === true;

/** Predicate which returns true if the object is an internal [[StateObject]] object */
static isState = (obj: any): obj is StateObject =>
isObject(obj['__stateObjectCache']);

Expand Down
4 changes: 2 additions & 2 deletions src/state/stateQueueManager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @module state */ /** for typedoc */
import { inArray } from "../common/common";
import { isString } from "../common/predicates";
import { StateDeclaration } from "./interface";
import { StateDeclaration, _StateDeclaration } from "./interface";
import { StateObject } from "./stateObject";
import { StateBuilder } from "./stateBuilder";
import { StateRegistryListener, StateRegistry } from "./stateRegistry";
Expand Down Expand Up @@ -30,7 +30,7 @@ export class StateQueueManager implements Disposable {
this.queue = [];
}

register(stateDecl: StateDeclaration) {
register(stateDecl: _StateDeclaration) {
let queue = this.queue;
let state = StateObject.create(stateDecl);
let name = state.name;
Expand Down
10 changes: 5 additions & 5 deletions src/state/stateRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { StateObject } from "./stateObject";
import { StateMatcher } from "./stateMatcher";
import { StateBuilder } from "./stateBuilder";
import { StateQueueManager } from "./stateQueueManager";
import { StateDeclaration } from "./interface";
import { StateDeclaration, _StateDeclaration } from "./interface";
import { BuilderFunction } from "./stateBuilder";
import { StateOrName } from "./interface";
import { removeFrom } from "../common/common";
Expand Down Expand Up @@ -107,9 +107,9 @@ export class StateRegistry {
*
* Gets the root of the state tree.
* The root state is implicitly created by UI-Router.
* Note: this returns the internal [[State]] representation, not a [[StateDeclaration]]
* Note: this returns the internal [[StateObject]] representation, not a [[StateDeclaration]]
*
* @return the root [[State]]
* @return the root [[StateObject]]
*/
root() {
return this._root;
Expand All @@ -123,11 +123,11 @@ export class StateRegistry {
* Note: a state will be queued if the state's parent isn't yet registered.
*
* @param stateDefinition the definition of the state to register.
* @returns the internal [[State]] object.
* @returns the internal [[StateObject]] object.
* If the state was successfully registered, then the object is fully built (See: [[StateBuilder]]).
* If the state was only queued, then the object is not fully built.
*/
register(stateDefinition: StateDeclaration): StateObject {
register(stateDefinition: _StateDeclaration): StateObject {
return this.stateQueue.register(stateDefinition);
}

Expand Down
2 changes: 1 addition & 1 deletion src/state/stateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class StateService {
*/
get current() { return this.router.globals.current; }
/**
* The current [[State]]
* The current [[StateObject]]
*
* This is a passthrough through to [[UIRouterGlobals.$current]]
*/
Expand Down
2 changes: 1 addition & 1 deletion src/state/targetState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { isString } from "../common/predicates";
* 4) the registered state object (the [[StateDeclaration]])
*
* Many UI-Router APIs such as [[StateService.go]] take a [[StateOrName]] argument which can
* either be a *state object* (a [[StateDeclaration]] or [[State]]) or a *state name* (a string).
* either be a *state object* (a [[StateDeclaration]] or [[StateObject]]) or a *state name* (a string).
* The `TargetState` class normalizes those options.
*
* A `TargetState` may be valid (the state being targeted exists in the registry)
Expand Down
4 changes: 2 additions & 2 deletions src/transition/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export interface TransitionHookFn {
* As each lifecycle event occurs, the hooks which are registered for the event and that state are called (in priority order).
*
* @param transition the current [[Transition]]
* @param state the [[State]] that the hook is bound to
* @param state the [[StateObject]] that the hook is bound to
* @param injector (for ng1 or ng2 only) the injector service
*
* @returns a [[HookResult]] which may alter the transition
Expand Down Expand Up @@ -700,7 +700,7 @@ export interface IHookRegistry {
_registeredHooks: { [key: string]: RegisteredHook[] }
}

/** A predicate type which takes a [[State]] and returns a boolean */
/** A predicate type which takes a [[StateObject]] and returns a boolean */
export type IStateMatch = Predicate<StateObject>
/**
* This object is used to configure whether or not a Transition Hook is invoked for a particular transition,
Expand Down
2 changes: 1 addition & 1 deletion src/url/urlRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
* - `string`
* - [[UrlMatcher]]
* - `RegExp`
* - [[State]]
* - [[StateObject]]
* @internalapi
*/
export class UrlRuleFactory {
Expand Down
2 changes: 1 addition & 1 deletion src/view/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface ActiveUIView {
*
* A `ViewConfig` is the runtime definition of a single view.
*
* During a transition, `ViewConfig`s are created for each [[_ViewDeclaration]] defined on each "entering" [[State]].
* During a transition, `ViewConfig`s are created for each [[_ViewDeclaration]] defined on each "entering" [[StateObject]].
* Then, the [[ViewService]] finds any matching `ui-view`(s) in the DOM, and supplies the ui-view
* with the `ViewConfig`. The `ui-view` then loads itself using the information found in the `ViewConfig`.
*
Expand Down

0 comments on commit 3a5d055

Please sign in to comment.