ui-router
Version:
State-based routing for Javascript
317 lines (271 loc) • 11.5 kB
text/typescript
/**
* # UI-Router for Angular 1
*
* - Provides an implementation for the [[CoreServices]] API, based on angular 1 services.
* - Also registers some services with the angular 1 injector.
* - Creates and bootstraps a new [[UIRouter]] object. Ties it to the the angular 1 lifecycle.
*
* @module ng1
* @preferred
*/
/** for typedoc */
import {UIRouter} from "../router";
import {services} from "../common/coreservices";
import {map, bindFunctions, removeFrom, find, noop} from "../common/common";
import {prop, propEq} from "../common/hof";
import {isObject} from "../common/predicates";
import {Node} from "../path/module";
import {Resolvable, ResolveContext} from "../resolve/module";
import {State} from "../state/module";
import {trace} from "../common/trace";
import {ng1ViewsBuilder, ng1ViewConfigFactory, Ng1ViewConfig} from "./viewsBuilder";
import {TemplateFactory} from "./templateFactory";
/** @hidden */
let app = angular.module("ui.router.angular1", []);
/**
* @ngdoc overview
* @name ui.router.util
*
* @description
* # ui.router.util sub-module
*
* This module is a dependency of other sub-modules. Do not include this module as a dependency
* in your angular app (use {@link ui.router} module instead).
*
*/
angular.module('ui.router.util', ['ng', 'ui.router.init']);
/**
* @ngdoc overview
* @name ui.router.router
*
* @requires ui.router.util
*
* @description
* # ui.router.router sub-module
*
* This module is a dependency of other sub-modules. Do not include this module as a dependency
* in your angular app (use {@link ui.router} module instead).
*/
angular.module('ui.router.router', ['ui.router.util']);
/**
* @ngdoc overview
* @name ui.router.state
*
* @requires ui.router.router
* @requires ui.router.util
*
* @description
* # ui.router.state sub-module
*
* This module is a dependency of the main ui.router module. Do not include this module as a dependency
* in your angular app (use {@link ui.router} module instead).
*
*/
angular.module('ui.router.state', ['ui.router.router', 'ui.router.util', 'ui.router.angular1']);
/**
* @ngdoc overview
* @name ui.router
*
* @requires ui.router.state
*
* @description
* # ui.router
*
* ## The main module for ui.router
* There are several sub-modules included with the ui.router module, however only this module is needed
* as a dependency within your angular app. The other modules are for organization purposes.
*
* The modules are:
* * ui.router - the main "umbrella" module
* * ui.router.router -
*
* *You'll need to include **only** this module as the dependency within your angular app.*
*
* <pre>
* <!doctype html>
* <html ng-app="myApp">
* <head>
* <script src="js/angular.js"></script>
* <!-- Include the ui-router script -->
* <script src="js/angular-ui-router.min.js"></script>
* <script>
* // ...and add 'ui.router' as a dependency
* var myApp = angular.module('myApp', ['ui.router']);
* </script>
* </head>
* <body>
* </body>
* </html>
* </pre>
*/
angular.module('ui.router', ['ui.router.init', 'ui.router.state', 'ui.router.angular1']);
angular.module('ui.router.compat', ['ui.router']);
/**
* Annotates a controller expression (may be a controller function(), a "controllername",
* or "controllername as name")
*
* - Temporarily decorates $injector.instantiate.
* - Invokes $controller() service
* - Calls $injector.instantiate with controller constructor
* - Annotate constructor
* - Undecorate $injector
*
* returns an array of strings, which are the arguments of the controller expression
*/
export function annotateController(controllerExpression): string[] {
let $injector = services.$injector;
let $controller = $injector.get("$controller");
let oldInstantiate = $injector.instantiate;
try {
let deps;
$injector.instantiate = function fakeInstantiate(constructorFunction) {
$injector.instantiate = oldInstantiate; // Un-decorate ASAP
deps = $injector.annotate(constructorFunction);
};
$controller(controllerExpression, { $scope: {} });
return deps;
} finally {
$injector.instantiate = oldInstantiate;
}
}
runBlock.$inject = ['$injector', '$q'];
function runBlock($injector, $q) {
services.$injector = $injector;
services.$q = $q;
}
app.run(runBlock);
let router: UIRouter = null;
ng1UIRouter.$inject = ['$locationProvider'];
/** This angular 1 provider instantiates a Router and exposes its services via the angular injector */
function ng1UIRouter($locationProvider) {
// Create a new instance of the Router when the ng1UIRouterProvider is initialized
router = new UIRouter();
// Apply ng1 `views` builder to the StateBuilder
router.stateRegistry.decorator("views", ng1ViewsBuilder);
router.viewService.viewConfigFactory('ng1', ng1ViewConfigFactory);
// Bind LocationConfig.hashPrefix to $locationProvider.hashPrefix
bindFunctions($locationProvider, services.locationConfig, $locationProvider, ['hashPrefix']);
// Create a LocationService.onChange registry
let urlListeners: Function[] = [];
services.location.onChange = (callback) => {
urlListeners.push(callback);
return () => removeFrom(urlListeners)(callback);
};
this.$get = $get;
$get.$inject = ['$location', '$browser', '$sniffer', '$rootScope', '$http', '$templateCache'];
function $get($location, $browser, $sniffer, $rootScope, $http, $templateCache) {
// Bind $locationChangeSuccess to the listeners registered in LocationService.onChange
$rootScope.$on("$locationChangeSuccess", evt => urlListeners.forEach(fn => fn(evt)));
// Bind LocationConfig.html5Mode to $locationProvider.html5Mode and $sniffer.history
services.locationConfig.html5Mode = function() {
let html5Mode = $locationProvider.html5Mode();
html5Mode = isObject(html5Mode) ? html5Mode.enabled : html5Mode;
return html5Mode && $sniffer.history;
};
services.template.get = (url: string) =>
$http.get(url, { cache: $templateCache, headers: { Accept: 'text/html' }}).then(prop("data"));
// Bind these LocationService functions to $location
bindFunctions($location, services.location, $location, ["replace", "url", "path", "search", "hash"]);
// Bind these LocationConfig functions to $location
bindFunctions($location, services.locationConfig, $location, ['port', 'protocol', 'host']);
// Bind these LocationConfig functions to $browser
bindFunctions($browser, services.locationConfig, $browser, ['baseHref']);
return router;
}
}
const resolveFactory = () => ({
/**
* This emulates most of the behavior of the ui-router 0.2.x $resolve.resolve() service API.
* @param invocables an object, with keys as resolve names and values as injectable functions
* @param locals key/value pre-resolved data (locals)
* @param parent a promise for a "parent resolve"
*/
resolve: (invocables, locals = {}, parent?) => {
let parentNode = new Node(new State(<any> { params: {} }));
let node = new Node(new State(<any> { params: {} }));
let context = new ResolveContext([parentNode, node]);
context.addResolvables(Resolvable.makeResolvables(invocables), node.state);
const resolveData = (parentLocals) => {
const rewrap = _locals => Resolvable.makeResolvables(<any> map(_locals, local => () => local));
context.addResolvables(rewrap(parentLocals), parentNode.state);
context.addResolvables(rewrap(locals), node.state);
return context.resolvePath();
};
return parent ? parent.then(resolveData) : resolveData({});
}
});
function $stateParamsFactory(ng1UIRouter) {
return ng1UIRouter.globals.params;
}
// The 'ui.router' ng1 module depends on 'ui.router.init' module.
angular.module('ui.router.init', []).provider("ng1UIRouter", <any> ng1UIRouter);
// This effectively calls $get() to init when we enter runtime
angular.module('ui.router.init').run(['ng1UIRouter', function(ng1UIRouter) { }]);
// $urlMatcherFactory service and $urlMatcherFactoryProvider
angular.module('ui.router.util').provider('$urlMatcherFactory', ['ng1UIRouterProvider', () => router.urlMatcherFactory]);
angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
// $urlRouter service and $urlRouterProvider
function getUrlRouterProvider() {
router.urlRouterProvider["$get"] = function() {
router.urlRouter.update(true);
if (!this.interceptDeferred) router.urlRouter.listen();
return router.urlRouter;
};
return router.urlRouterProvider;
}
angular.module('ui.router.router').provider('$urlRouter', ['ng1UIRouterProvider', getUrlRouterProvider]);
angular.module('ui.router.router').run(['$urlRouter', function($urlRouter) { }]);
// $state service and $stateProvider
// $urlRouter service and $urlRouterProvider
function getStateProvider() {
router.stateProvider["$get"] = function() {
// Autoflush once we are in runtime
router.stateRegistry.stateQueue.autoFlush(router.stateService);
return router.stateService;
};
return router.stateProvider;
}
angular.module('ui.router.state').provider('$state', ['ng1UIRouterProvider', getStateProvider]);
angular.module('ui.router.state').run(['$state', function($state) { }]);
// $stateParams service
angular.module('ui.router.state').factory('$stateParams', ['ng1UIRouter', (ng1UIRouter) =>
ng1UIRouter.globals.params]);
// $transitions service and $transitionsProvider
function getTransitionsProvider() {
loadAllControllerLocals.$inject = ['$transition$'];
function loadAllControllerLocals($transition$) {
const loadLocals = (vc: Ng1ViewConfig) => {
let node = (<Node> find($transition$.treeChanges().to, propEq('state', vc.viewDecl.$context)));
// Temporary fix; This whole callback should be nuked when fixing #2662
if (!node) return services.$q.when();
let resolveCtx = node.resolveContext;
let controllerDeps = annotateController(vc.controller);
let resolvables = resolveCtx.getResolvables();
function $loadControllerLocals() { }
$loadControllerLocals.$inject = controllerDeps.filter(dep => resolvables.hasOwnProperty(dep));
// Load any controller resolves that aren't already loaded
return resolveCtx.invokeLater($loadControllerLocals)
// Then provide the view config with all the resolved data
.then(() => vc.locals = map(resolvables, res => res.data));
};
let loadAllLocals = $transition$.views("entering").filter(vc => !!vc.controller).map(loadLocals);
return services.$q.all(loadAllLocals).then(noop);
}
router.transitionService.onFinish({}, loadAllControllerLocals);
router.transitionService["$get"] = () => router.transitionService;
return router.transitionService;
}
angular.module('ui.router.state').provider('$transitions', ['ng1UIRouterProvider', getTransitionsProvider]);
// $templateFactory service
angular.module('ui.router.util').factory('$templateFactory', ['ng1UIRouter', () => new TemplateFactory()]);
// The $view service
angular.module('ui.router').factory('$view', () => router.viewService);
// The old $resolve service
angular.module('ui.router').factory('$resolve', <any> resolveFactory);
// $trace service
angular.module("ui.router").service("$trace", () => trace);
watchDigests.$inject = ['$rootScope'];
export function watchDigests($rootScope) {
$rootScope.$watch(function() { trace.approximateDigests++; });
}
angular.module("ui.router").run(watchDigests);