refactor(angular_1_router): use directives for route targets
BREAKING CHANGE: Previously, route configuration took a controller constructor function as the value of `component` in a route definition: ``` $route.config([ { route: '/', component: MyController } ]) ``` Based on the name of the controller, we used to use a componentMapper service to determine what template to pair with each controller, how to bind the instance to the $scope. To make the 1.x router more semantically alligned with Angular 2, we now route to a directive. Thus a route configuration takes a normalized directive name: ``` $route.config([ { route: '/', component: 'myDirective' } ]) ``` BREAKING CHANGE: In order to avoid name collisions, lifecycle hooks are now prefixed with `$`. Before: ``` MyController.prototype.onActivate = ... ``` After: ``` MyController.prototype.$onActivate = ... ``` Same for `$canActivate` (which now lives on the directive factory function), `$canDeactivate`, `$canReuse`, and `$onDeactivate` hooks.
This commit is contained in:
parent
6e0ca7f39a
commit
5205a9e65f
|
@ -22,12 +22,12 @@ var PRELUDE = '(function(){\n';
|
|||
var POSTLUDE = '\n}());\n';
|
||||
var FACADES = fs.readFileSync(__dirname + '/lib/facades.es5', 'utf8');
|
||||
var DIRECTIVES = fs.readFileSync(__dirname + '/src/ng_outlet.js', 'utf8');
|
||||
var moduleTemplate = fs.readFileSync(__dirname + '/src/module_template.js', 'utf8');
|
||||
|
||||
function main() {
|
||||
var ES6_SHIM = fs.readFileSync(__dirname + '/../../node_modules/es6-shim/es6-shim.js', 'utf8');
|
||||
var dir = __dirname + '/../angular2/src/router/';
|
||||
|
||||
var out = '';
|
||||
|
||||
var sharedCode = '';
|
||||
files.forEach(function (file) {
|
||||
var moduleName = 'router/' + file.replace(/\.ts$/, '');
|
||||
|
@ -35,57 +35,9 @@ function main() {
|
|||
sharedCode += transform(moduleName, fs.readFileSync(dir + file, 'utf8'));
|
||||
});
|
||||
|
||||
out += "angular.module('ngComponentRouter')";
|
||||
out += angularFactory('$router', ['$q', '$location', '$$controllerIntrospector',
|
||||
'$browser', '$rootScope', '$injector'], [
|
||||
FACADES,
|
||||
"var exports = {Injectable: function () {}};",
|
||||
"var require = function () {return exports;};",
|
||||
sharedCode,
|
||||
"var RouteConfig = exports.RouteConfig;",
|
||||
"angular.annotations = {RouteConfig: RouteConfig, CanActivate: exports.CanActivate};",
|
||||
"angular.stringifyInstruction = exports.stringifyInstruction;",
|
||||
"var RouteRegistry = exports.RouteRegistry;",
|
||||
"var RootRouter = exports.RootRouter;",
|
||||
//TODO: move this code into a templated JS file
|
||||
"var registry = new RouteRegistry();",
|
||||
"var location = new Location();",
|
||||
var out = moduleTemplate.replace('//{{FACADES}}', FACADES).replace('//{{SHARED_CODE}}', sharedCode);
|
||||
|
||||
"$$controllerIntrospector(function (name, constructor) {",
|
||||
"if (constructor.$canActivate) {",
|
||||
"constructor.annotations = constructor.annotations || [];",
|
||||
"constructor.annotations.push(new angular.annotations.CanActivate(function (instruction) {",
|
||||
"return $injector.invoke(constructor.$canActivate, constructor, {",
|
||||
"$routeParams: instruction.component ? instruction.component.params : instruction.params",
|
||||
"});",
|
||||
"}));",
|
||||
"}",
|
||||
"if (constructor.$routeConfig) {",
|
||||
"constructor.annotations = constructor.annotations || [];",
|
||||
"constructor.annotations.push(new angular.annotations.RouteConfig(constructor.$routeConfig));",
|
||||
"}",
|
||||
"if (constructor.annotations) {",
|
||||
"constructor.annotations.forEach(function(annotation) {",
|
||||
"if (annotation instanceof RouteConfig) {",
|
||||
"annotation.configs.forEach(function (config) {",
|
||||
"registry.config(constructor, config);",
|
||||
"});",
|
||||
"}",
|
||||
"});",
|
||||
"}",
|
||||
"});",
|
||||
|
||||
"var router = new RootRouter(registry, location, new Object());",
|
||||
"$rootScope.$watch(function () { return $location.path(); }, function (path) {",
|
||||
"if (router.lastNavigationAttempt !== path) {",
|
||||
"router.navigateByUrl(path);",
|
||||
"}",
|
||||
"});",
|
||||
|
||||
"return router;"
|
||||
].join('\n'));
|
||||
|
||||
return PRELUDE + ES6_SHIM + DIRECTIVES + out + POSTLUDE;
|
||||
return PRELUDE + DIRECTIVES + out + POSTLUDE;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -32,6 +32,10 @@ function isArray(obj) {
|
|||
return Array.isArray(obj);
|
||||
}
|
||||
|
||||
function getTypeNameForDebugging (fn) {
|
||||
return fn.name || 'Root';
|
||||
}
|
||||
|
||||
var PromiseWrapper = {
|
||||
resolve: function (reason) {
|
||||
return $q.when(reason);
|
||||
|
@ -252,7 +256,7 @@ var StringWrapper = {
|
|||
},
|
||||
|
||||
startsWith: function(s, start) {
|
||||
return s.startsWith(start);
|
||||
return s.substr(0, start.length) === start;
|
||||
},
|
||||
|
||||
replaceAllMapped: function(s, from, cb) {
|
||||
|
@ -272,14 +276,18 @@ var StringWrapper = {
|
|||
|
||||
//TODO: implement?
|
||||
// I think it's too heavy to ask 1.x users to bring in Rx for the router...
|
||||
function EventEmitter() {
|
||||
|
||||
}
|
||||
function EventEmitter() {}
|
||||
|
||||
var BaseException = Error;
|
||||
|
||||
var ObservableWrapper = {
|
||||
callNext: function(){}
|
||||
callNext: function(ob, val) {
|
||||
ob.fn(val);
|
||||
},
|
||||
|
||||
subscribe: function(ob, fn) {
|
||||
ob.fn = fn;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: https://github.com/angular/angular.js/blob/master/src/ng/browser.js#L227-L265
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
|
||||
angular.module('ngComponentRouter').
|
||||
value('$route', null). // can be overloaded with ngRouteShim
|
||||
factory('$router', ['$q', '$location', '$$directiveIntrospector', '$browser', '$rootScope', '$injector', '$route', routerFactory]);
|
||||
|
||||
function routerFactory($q, $location, $$directiveIntrospector, $browser, $rootScope, $injector) {
|
||||
|
||||
// When this file is processed, the line below is replaced with
|
||||
// the contents of `../lib/facades.es5`.
|
||||
//{{FACADES}}
|
||||
|
||||
var exports = {Injectable: function () {}};
|
||||
var require = function () {return exports;};
|
||||
|
||||
// When this file is processed, the line below is replaced with
|
||||
// the contents of the compiled TypeScript classes.
|
||||
//{{SHARED_CODE}}
|
||||
|
||||
//TODO: this is a hack to replace the exiting implementation at run-time
|
||||
exports.getCanActivateHook = function (directiveName) {
|
||||
var factory = $$directiveIntrospector.getTypeByName(directiveName);
|
||||
return factory && factory.$canActivate && function (next, prev) {
|
||||
return $injector.invoke(factory.$canActivate, null, {
|
||||
$nextInstruction: next,
|
||||
$prevInstruction: prev
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// This hack removes assertions about the type of the "component"
|
||||
// property in a route config
|
||||
exports.assertComponentExists = function () {};
|
||||
|
||||
angular.stringifyInstruction = exports.stringifyInstruction;
|
||||
|
||||
var RouteRegistry = exports.RouteRegistry;
|
||||
var RootRouter = exports.RootRouter;
|
||||
|
||||
var registry = new RouteRegistry();
|
||||
var location = new Location();
|
||||
|
||||
$$directiveIntrospector(function (name, factory) {
|
||||
if (angular.isArray(factory.$routeConfig)) {
|
||||
factory.$routeConfig.forEach(function (config) {
|
||||
registry.config(name, config);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Because Angular 1 has no notion of a root component, we use an object with unique identity
|
||||
// to represent this.
|
||||
var ROOT_COMPONENT_OBJECT = new Object();
|
||||
|
||||
var router = new RootRouter(registry, location, ROOT_COMPONENT_OBJECT);
|
||||
$rootScope.$watch(function () { return $location.path(); }, function (path) {
|
||||
if (router.lastNavigationAttempt !== path) {
|
||||
router.navigateByUrl(path);
|
||||
}
|
||||
});
|
||||
|
||||
router.subscribe(function () {
|
||||
$rootScope.$broadcast('$routeChangeSuccess', {});
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
|
@ -4,69 +4,63 @@
|
|||
* A module for adding new a routing system Angular 1.
|
||||
*/
|
||||
angular.module('ngComponentRouter', [])
|
||||
.factory('$componentMapper', $componentMapperFactory)
|
||||
.directive('ngOutlet', ngOutletDirective)
|
||||
.directive('ngOutlet', ngOutletFillContentDirective)
|
||||
.directive('ngLink', ngLinkDirective)
|
||||
.directive('a', anchorLinkDirective); // TODO: make the anchor link feature configurable
|
||||
.directive('ngLink', ngLinkDirective);
|
||||
|
||||
/*
|
||||
* A module for inspecting controller constructors
|
||||
*/
|
||||
angular.module('ng')
|
||||
.provider('$$controllerIntrospector', $$controllerIntrospectorProvider)
|
||||
.config(controllerProviderDecorator);
|
||||
.provider('$$directiveIntrospector', $$directiveIntrospectorProvider)
|
||||
.config(compilerProviderDecorator);
|
||||
|
||||
/*
|
||||
* decorates with routing info
|
||||
* decorates $compileProvider so that we have access to routing metadata
|
||||
*/
|
||||
function controllerProviderDecorator($controllerProvider, $$controllerIntrospectorProvider) {
|
||||
var register = $controllerProvider.register;
|
||||
$controllerProvider.register = function (name, ctrl) {
|
||||
$$controllerIntrospectorProvider.register(name, ctrl);
|
||||
return register.apply(this, arguments);
|
||||
function compilerProviderDecorator($compileProvider, $$directiveIntrospectorProvider) {
|
||||
var directive = $compileProvider.directive;
|
||||
$compileProvider.directive = function (name, factory) {
|
||||
$$directiveIntrospectorProvider.register(name, factory);
|
||||
return directive.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: decorate $controller ?
|
||||
|
||||
/*
|
||||
* private service that holds route mappings for each controller
|
||||
*/
|
||||
function $$controllerIntrospectorProvider() {
|
||||
var controllers = [];
|
||||
var controllersByName = {};
|
||||
var onControllerRegistered = null;
|
||||
function $$directiveIntrospectorProvider() {
|
||||
var directiveBuffer = [];
|
||||
var directiveFactoriesByName = {};
|
||||
var onDirectiveRegistered = null;
|
||||
return {
|
||||
register: function (name, constructor) {
|
||||
if (angular.isArray(constructor)) {
|
||||
constructor = constructor[constructor.length - 1];
|
||||
register: function (name, factory) {
|
||||
if (angular.isArray(factory)) {
|
||||
factory = factory[factory.length - 1];
|
||||
}
|
||||
controllersByName[name] = constructor;
|
||||
constructor.$$controllerName = name;
|
||||
if (onControllerRegistered) {
|
||||
onControllerRegistered(name, constructor);
|
||||
directiveFactoriesByName[name] = factory;
|
||||
if (onDirectiveRegistered) {
|
||||
onDirectiveRegistered(name, factory);
|
||||
} else {
|
||||
controllers.push({name: name, constructor: constructor});
|
||||
directiveBuffer.push({name: name, factory: factory});
|
||||
}
|
||||
},
|
||||
$get: ['$componentMapper', function ($componentMapper) {
|
||||
$get: function () {
|
||||
var fn = function (newOnControllerRegistered) {
|
||||
onControllerRegistered = function (name, constructor) {
|
||||
name = $componentMapper.component(name);
|
||||
return newOnControllerRegistered(name, constructor);
|
||||
};
|
||||
while (controllers.length > 0) {
|
||||
var rule = controllers.pop();
|
||||
onControllerRegistered(rule.name, rule.constructor);
|
||||
onDirectiveRegistered = newOnControllerRegistered;
|
||||
while (directiveBuffer.length > 0) {
|
||||
var directive = directiveBuffer.pop();
|
||||
onDirectiveRegistered(directive.name, directive.factory);
|
||||
}
|
||||
};
|
||||
|
||||
fn.getTypeByName = function (name) {
|
||||
return controllersByName[name];
|
||||
return directiveFactoriesByName[name];
|
||||
};
|
||||
|
||||
return fn;
|
||||
}]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -85,7 +79,7 @@ function $$controllerIntrospectorProvider() {
|
|||
*
|
||||
* The value for the `ngOutlet` attribute is optional.
|
||||
*/
|
||||
function ngOutletDirective($animate, $q, $router, $componentMapper, $controller, $templateRequest) {
|
||||
function ngOutletDirective($animate, $q, $router) {
|
||||
var rootRouter = $router;
|
||||
|
||||
return {
|
||||
|
@ -105,10 +99,12 @@ function ngOutletDirective($animate, $q, $router, $componentMapper, $controller,
|
|||
myCtrl = ctrls[1],
|
||||
router = (parentCtrl && parentCtrl.$$router) || rootRouter;
|
||||
|
||||
myCtrl.$$currentComponent = null;
|
||||
|
||||
var childRouter,
|
||||
currentController,
|
||||
currentInstruction,
|
||||
currentScope,
|
||||
currentController,
|
||||
currentElement,
|
||||
previousLeaveAnimation;
|
||||
|
||||
|
@ -136,8 +132,8 @@ function ngOutletDirective($animate, $q, $router, $componentMapper, $controller,
|
|||
var next = $q.when(true);
|
||||
var previousInstruction = currentInstruction;
|
||||
currentInstruction = instruction;
|
||||
if (currentController.onReuse) {
|
||||
next = $q.when(currentController.onReuse(currentInstruction, previousInstruction));
|
||||
if (currentController && currentController.$onReuse) {
|
||||
next = $q.when(currentController.$onReuse(currentInstruction, previousInstruction));
|
||||
}
|
||||
|
||||
return next;
|
||||
|
@ -147,8 +143,8 @@ function ngOutletDirective($animate, $q, $router, $componentMapper, $controller,
|
|||
if (!currentInstruction ||
|
||||
currentInstruction.componentType !== nextInstruction.componentType) {
|
||||
result = false;
|
||||
} else if (currentController.canReuse) {
|
||||
result = currentController.canReuse(nextInstruction, currentInstruction);
|
||||
} else if (currentController && currentController.$canReuse) {
|
||||
result = currentController.$canReuse(nextInstruction, currentInstruction);
|
||||
} else {
|
||||
result = nextInstruction === currentInstruction ||
|
||||
angular.equals(nextInstruction.params, currentInstruction.params);
|
||||
|
@ -156,60 +152,59 @@ function ngOutletDirective($animate, $q, $router, $componentMapper, $controller,
|
|||
return $q.when(result);
|
||||
},
|
||||
canDeactivate: function (instruction) {
|
||||
if (currentInstruction && currentController && currentController.canDeactivate) {
|
||||
return $q.when(currentController.canDeactivate(instruction, currentInstruction));
|
||||
if (currentController && currentController.$canDeactivate) {
|
||||
return $q.when(currentController.$canDeactivate(instruction, currentInstruction));
|
||||
}
|
||||
return $q.when(true);
|
||||
},
|
||||
deactivate: function (instruction) {
|
||||
if (currentController && currentController.onDeactivate) {
|
||||
return $q.when(currentController.onDeactivate(instruction, currentInstruction));
|
||||
if (currentController && currentController.$onDeactivate) {
|
||||
return $q.when(currentController.$onDeactivate(instruction, currentInstruction));
|
||||
}
|
||||
return $q.when();
|
||||
},
|
||||
activate: function (instruction) {
|
||||
var previousInstruction = currentInstruction;
|
||||
currentInstruction = instruction;
|
||||
childRouter = router.childRouter(instruction.componentType);
|
||||
|
||||
var controllerConstructor, componentName;
|
||||
controllerConstructor = instruction.componentType;
|
||||
componentName = $componentMapper.component(controllerConstructor.$$controllerName);
|
||||
var componentName = myCtrl.$$componentName = instruction.componentType;
|
||||
|
||||
if (typeof componentName != 'string') {
|
||||
throw new Error('Component is not a string for ' + instruction.urlPath);
|
||||
}
|
||||
|
||||
myCtrl.$$routeParams = instruction.params;
|
||||
|
||||
myCtrl.$$template = '<div ' + dashCase(componentName) + '></div>';
|
||||
|
||||
myCtrl.$$router = router.childRouter(instruction.componentType);
|
||||
|
||||
var componentTemplateUrl = $componentMapper.template(componentName);
|
||||
return $templateRequest(componentTemplateUrl).then(function (templateHtml) {
|
||||
myCtrl.$$router = childRouter;
|
||||
myCtrl.$$template = templateHtml;
|
||||
}).then(function () {
|
||||
var newScope = scope.$new();
|
||||
var locals = {
|
||||
$scope: newScope,
|
||||
$router: childRouter,
|
||||
$routeParams: (instruction.params || {})
|
||||
};
|
||||
|
||||
// todo(shahata): controllerConstructor is not minify friendly
|
||||
currentController = $controller(controllerConstructor, locals);
|
||||
|
||||
var clone = $transclude(newScope, function (clone) {
|
||||
$animate.enter(clone, null, currentElement || $element);
|
||||
cleanupLastView();
|
||||
});
|
||||
|
||||
var controllerAs = $componentMapper.controllerAs(componentName) || componentName;
|
||||
newScope[controllerAs] = currentController;
|
||||
|
||||
currentElement = clone;
|
||||
currentScope = newScope;
|
||||
|
||||
if (currentController.onActivate) {
|
||||
return currentController.onActivate(instruction, previousInstruction);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// TODO: prefer the other directive retrieving the controller
|
||||
// by debug mode
|
||||
currentController = currentElement.children().eq(0).controller(componentName);
|
||||
|
||||
if (currentController && currentController.$onActivate) {
|
||||
return currentController.$onActivate(instruction, previousInstruction);
|
||||
}
|
||||
return $q.when();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This directive is responsible for compiling the contents of ng-outlet
|
||||
*/
|
||||
function ngOutletFillContentDirective($compile) {
|
||||
return {
|
||||
restrict: 'EA',
|
||||
|
@ -220,6 +215,15 @@ function ngOutletFillContentDirective($compile) {
|
|||
$element.html(template);
|
||||
var link = $compile($element.contents());
|
||||
link(scope);
|
||||
|
||||
// TODO: move to primary directive
|
||||
var componentInstance = scope[ctrl.$$componentName];
|
||||
if (componentInstance) {
|
||||
ctrl.$$currentComponent = componentInstance;
|
||||
|
||||
componentInstance.$router = ctrl.$$router;
|
||||
componentInstance.$routeParams = ctrl.$$routeParams;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -249,7 +253,7 @@ function ngOutletFillContentDirective($compile) {
|
|||
* </div>
|
||||
* ```
|
||||
*/
|
||||
function ngLinkDirective($router, $location, $parse) {
|
||||
function ngLinkDirective($router, $parse) {
|
||||
var rootRouter = $router;
|
||||
|
||||
return {
|
||||
|
@ -264,10 +268,12 @@ function ngLinkDirective($router, $location, $parse) {
|
|||
return;
|
||||
}
|
||||
|
||||
var instruction = null;
|
||||
var link = attrs.ngLink || '';
|
||||
|
||||
function getLink(params) {
|
||||
return './' + angular.stringifyInstruction(router.generate(params));
|
||||
instruction = router.generate(params);
|
||||
return './' + angular.stringifyInstruction(instruction);
|
||||
}
|
||||
|
||||
var routeParamsGetter = $parse(link);
|
||||
|
@ -282,128 +288,16 @@ function ngLinkDirective($router, $location, $parse) {
|
|||
elt.attr('href', getLink(params));
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function anchorLinkDirective($router) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function (scope, element) {
|
||||
// If the linked element is not an anchor tag anymore, do nothing
|
||||
if (element[0].nodeName.toLowerCase() !== 'a') {
|
||||
elt.on('click', function (event) {
|
||||
if (event.which !== 1 || !instruction) {
|
||||
return;
|
||||
}
|
||||
|
||||
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
|
||||
var hrefAttrName = Object.prototype.toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
|
||||
'xlink:href' : 'href';
|
||||
|
||||
element.on('click', function (event) {
|
||||
if (event.which !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var href = element.attr(hrefAttrName);
|
||||
if (href && $router.recognize(href)) {
|
||||
$router.navigateByUrl(href);
|
||||
$router.navigateByInstruction(instruction);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @name $componentMapperFactory
|
||||
* @description
|
||||
*
|
||||
* This lets you configure conventions for what controllers are named and where to load templates from.
|
||||
*
|
||||
* The default behavior is to dasherize and serve from `./components`. A component called `myWidget`
|
||||
* uses a controller named `MyWidgetController` and a template loaded from `./components/my-widget/my-widget.html`.
|
||||
*
|
||||
* A component is:
|
||||
* - a controller
|
||||
* - a template
|
||||
* - an optional router
|
||||
*
|
||||
* This service makes it easy to group all of them into a single concept.
|
||||
*/
|
||||
function $componentMapperFactory() {
|
||||
|
||||
var DEFAULT_SUFFIX = 'Controller';
|
||||
|
||||
var componentToCtrl = function componentToCtrlDefault(name) {
|
||||
return name[0].toUpperCase() + name.substr(1) + DEFAULT_SUFFIX;
|
||||
};
|
||||
|
||||
var componentToTemplate = function componentToTemplateDefault(name) {
|
||||
var dashName = dashCase(name);
|
||||
return './components/' + dashName + '/' + dashName + '.html';
|
||||
};
|
||||
|
||||
var ctrlToComponent = function ctrlToComponentDefault(name) {
|
||||
return name[0].toLowerCase() + name.substr(1, name.length - DEFAULT_SUFFIX.length - 1);
|
||||
};
|
||||
|
||||
var componentToControllerAs = function componentToControllerAsDefault(name) {
|
||||
return name;
|
||||
};
|
||||
|
||||
return {
|
||||
controllerName: function (name) {
|
||||
return componentToCtrl(name);
|
||||
},
|
||||
|
||||
controllerAs: function (name) {
|
||||
return componentToControllerAs(name);
|
||||
},
|
||||
|
||||
template: function (name) {
|
||||
return componentToTemplate(name);
|
||||
},
|
||||
|
||||
component: function (name) {
|
||||
return ctrlToComponent(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* @name $componentMapper#setCtrlNameMapping
|
||||
* @description takes a function for mapping component names to component controller names
|
||||
*/
|
||||
setCtrlNameMapping: function (newFn) {
|
||||
componentToCtrl = newFn;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @name $componentMapper#setCtrlAsMapping
|
||||
* @description takes a function for mapping component names to controllerAs name in the template
|
||||
*/
|
||||
setCtrlAsMapping: function (newFn) {
|
||||
componentToControllerAs = newFn;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @name $componentMapper#setComponentFromCtrlMapping
|
||||
* @description takes a function for mapping component controller names to component names
|
||||
*/
|
||||
setComponentFromCtrlMapping: function (newFn) {
|
||||
ctrlToComponent = newFn;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* @name $componentMapper#setTemplateMapping
|
||||
* @description takes a function for mapping component names to component template URLs
|
||||
*/
|
||||
setTemplateMapping: function (newFn) {
|
||||
componentToTemplate = newFn;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
describe('$componentMapper', function () {
|
||||
var elt,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$templateCache;
|
||||
|
||||
function Ctrl() {
|
||||
this.message = 'howdy';
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
module('ng');
|
||||
module('ngComponentRouter');
|
||||
module(function ($controllerProvider) {
|
||||
$controllerProvider.register('myComponentController', Ctrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('should convert a component name to a controller name', inject(function ($componentMapper) {
|
||||
expect($componentMapper.controllerName('foo')).toBe('FooController');
|
||||
}));
|
||||
|
||||
it('should convert a controller name to a component name', inject(function ($componentMapper) {
|
||||
expect($componentMapper.component('FooController')).toBe('foo');
|
||||
}));
|
||||
|
||||
it('should convert a component name to a template URL', inject(function ($componentMapper) {
|
||||
expect($componentMapper.template('foo')).toBe('./components/foo/foo.html');
|
||||
}));
|
||||
|
||||
it('should work with a controller constructor fn and a template url', inject(function ($componentMapper) {
|
||||
var routes = {};
|
||||
$componentMapper.setCtrlNameMapping(function (name) {
|
||||
return routes[name].controller;
|
||||
});
|
||||
$componentMapper.setTemplateMapping(function (name) {
|
||||
return routes[name].templateUrl;
|
||||
});
|
||||
$componentMapper.setCtrlAsMapping(function (name) {
|
||||
return 'ctrl';
|
||||
});
|
||||
|
||||
routes.myComponent = {
|
||||
controller: Ctrl,
|
||||
templateUrl: '/foo'
|
||||
};
|
||||
|
||||
inject(function(_$compile_, _$rootScope_, _$router_, _$templateCache_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
$templateCache = _$templateCache_;
|
||||
});
|
||||
|
||||
$templateCache.put('/foo', [200, '{{ctrl.message}}', {}]);
|
||||
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
$router.config([
|
||||
{ path: '/', component: Ctrl }
|
||||
]);
|
||||
|
||||
$router.navigateByUrl('/');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.text()).toBe('howdy');
|
||||
}));
|
||||
|
||||
function compile(template) {
|
||||
elt = $compile('<div>' + template + '</div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
return elt;
|
||||
}
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
describe('$$controllerIntrospector', function () {
|
||||
|
||||
var $controllerProvider;
|
||||
|
||||
beforeEach(function() {
|
||||
module('ng');
|
||||
module('ngComponentRouter');
|
||||
module(function(_$controllerProvider_) {
|
||||
$controllerProvider = _$controllerProvider_;
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the introspector function whenever a controller is registered', inject(function ($$controllerIntrospector) {
|
||||
var spy = jasmine.createSpy();
|
||||
$$controllerIntrospector(spy);
|
||||
function Ctrl(){}
|
||||
$controllerProvider.register('SomeController', Ctrl);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('some', Ctrl);
|
||||
}));
|
||||
|
||||
it('should call the introspector function whenever a controller is registered with array annotations', inject(function ($$controllerIntrospector) {
|
||||
var spy = jasmine.createSpy();
|
||||
$$controllerIntrospector(spy);
|
||||
function Ctrl(foo){}
|
||||
$controllerProvider.register('SomeController', ['foo', Ctrl]);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('some', Ctrl);
|
||||
}));
|
||||
|
||||
it('should retrieve a constructor', inject(function ($$controllerIntrospector) {
|
||||
function Ctrl(foo){}
|
||||
$controllerProvider.register('SomeController', ['foo', Ctrl]);
|
||||
expect($$controllerIntrospector.getTypeByName('SomeController')).toBe(Ctrl);
|
||||
}));
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
'use strict';
|
||||
|
||||
describe('$$directiveIntrospector', function () {
|
||||
|
||||
var $compileProvider;
|
||||
|
||||
beforeEach(function() {
|
||||
module('ng');
|
||||
module('ngComponentRouter');
|
||||
module(function(_$compileProvider_) {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the introspector function whenever a directive factory is registered', inject(function ($$directiveIntrospector) {
|
||||
var spy = jasmine.createSpy();
|
||||
$$directiveIntrospector(spy);
|
||||
function myDir(){}
|
||||
$compileProvider.directive('myDir', myDir);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('myDir', myDir);
|
||||
}));
|
||||
|
||||
it('should call the introspector function whenever a directive factory is registered with array annotations', inject(function ($$directiveIntrospector) {
|
||||
var spy = jasmine.createSpy();
|
||||
$$directiveIntrospector(spy);
|
||||
function myDir(){}
|
||||
$compileProvider.directive('myDir', ['foo', myDir]);
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('myDir', myDir);
|
||||
}));
|
||||
|
||||
it('should retrieve a factory based on directive name', inject(function ($$directiveIntrospector) {
|
||||
function myDir(){}
|
||||
$compileProvider.directive('myDir', ['foo', myDir]);
|
||||
expect($$directiveIntrospector.getTypeByName('myDir')).toBe(myDir);
|
||||
}));
|
||||
});
|
|
@ -6,31 +6,27 @@ describe('ngOutlet animations', function () {
|
|||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$templateCache,
|
||||
$controllerProvider;
|
||||
|
||||
function UserController($routeParams) {
|
||||
this.name = $routeParams.name;
|
||||
}
|
||||
$compileProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
module('ng');
|
||||
module('ngAnimate');
|
||||
module('ngAnimateMock');
|
||||
module('ngComponentRouter');
|
||||
module(function (_$controllerProvider_) {
|
||||
$controllerProvider = _$controllerProvider_;
|
||||
module(function (_$compileProvider_) {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
|
||||
inject(function (_$animate_, _$compile_, _$rootScope_, _$router_, _$templateCache_) {
|
||||
inject(function (_$animate_, _$compile_, _$rootScope_, _$router_) {
|
||||
$animate = _$animate_;
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
$templateCache = _$templateCache_;
|
||||
});
|
||||
|
||||
put('user', '<div>hello {{user.name}}</div>');
|
||||
$controllerProvider.register('UserController', UserController);
|
||||
registerComponent('userCmp', {
|
||||
template: '<div>hello {{userCmp.$routeParams.name}}</div>'
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
|
@ -43,7 +39,7 @@ describe('ngOutlet animations', function () {
|
|||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.config([
|
||||
{ path: '/user/:name', component: UserController }
|
||||
{ path: '/user/:name', component: 'userCmp' }
|
||||
]);
|
||||
|
||||
$router.navigateByUrl('/user/brian');
|
||||
|
@ -70,8 +66,32 @@ describe('ngOutlet animations', function () {
|
|||
expect(item.element.text()).toBe('hello brian');
|
||||
});
|
||||
|
||||
function put(name, template) {
|
||||
$templateCache.put(componentTemplatePath(name), [200, template, {}]);
|
||||
|
||||
function registerComponent(name, options) {
|
||||
var controller = options.controller || function () {};
|
||||
|
||||
['$onActivate', '$onDeactivate', '$onReuse', '$canReuse', '$canDeactivate'].forEach(function (hookName) {
|
||||
if (options[hookName]) {
|
||||
controller.prototype[hookName] = options[hookName];
|
||||
}
|
||||
});
|
||||
|
||||
function factory() {
|
||||
return {
|
||||
template: options.template || '',
|
||||
controllerAs: name,
|
||||
controller: controller
|
||||
};
|
||||
}
|
||||
|
||||
if (options.$canActivate) {
|
||||
factory.$canActivate = options.$canActivate;
|
||||
}
|
||||
if (options.$routeConfig) {
|
||||
factory.$routeConfig = options.$routeConfig;
|
||||
}
|
||||
|
||||
$compileProvider.directive(name, factory);
|
||||
}
|
||||
|
||||
function compile(template) {
|
|
@ -1,53 +1,45 @@
|
|||
'use strict';
|
||||
|
||||
describe('ngOutlet', function () {
|
||||
|
||||
describe('Navigation lifecycle', function () {
|
||||
var elt,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$templateCache,
|
||||
$controllerProvider,
|
||||
$componentMapperProvider;
|
||||
|
||||
var OneController, TwoController, UserController;
|
||||
|
||||
function instructionFor(componentType) {
|
||||
return jasmine.objectContaining({componentType: componentType});
|
||||
}
|
||||
|
||||
$compileProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
module('ng');
|
||||
module('ngComponentRouter');
|
||||
module(function (_$controllerProvider_, _$componentMapperProvider_) {
|
||||
$controllerProvider = _$controllerProvider_;
|
||||
$componentMapperProvider = _$componentMapperProvider_;
|
||||
module(function (_$compileProvider_) {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
|
||||
inject(function (_$compile_, _$rootScope_, _$router_, _$templateCache_) {
|
||||
inject(function (_$compile_, _$rootScope_, _$router_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
$templateCache = _$templateCache_;
|
||||
});
|
||||
|
||||
UserController = registerComponent('user', '<div>hello {{user.name}}</div>', function ($routeParams) {
|
||||
this.name = $routeParams.name;
|
||||
registerComponent('oneCmp', {
|
||||
template: '<div>{{oneCmp.number}}</div>',
|
||||
controller: function () {this.number = 'one'}
|
||||
});
|
||||
registerComponent('twoCmp', {
|
||||
template: '<div><a ng-link="[\'/Two\']">{{twoCmp.number}}</a></div>',
|
||||
controller: function () {this.number = 'two'}
|
||||
});
|
||||
OneController = registerComponent('one', '<div>{{one.number}}</div>', boringController('number', 'one'));
|
||||
TwoController = registerComponent('two', '<div>{{two.number}}</div>', boringController('number', 'two'));
|
||||
});
|
||||
|
||||
|
||||
it('should run the activate hook of controllers', function () {
|
||||
var spy = jasmine.createSpy('activate');
|
||||
var activate = registerComponent('activate', '', {
|
||||
onActivate: spy
|
||||
registerComponent('activateCmp', {
|
||||
template: '<p>hello</p>',
|
||||
$onActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: activate }
|
||||
{ path: '/a', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div>outer { <div ng-outlet></div> }</div>');
|
||||
|
||||
|
@ -60,31 +52,32 @@ describe('ngOutlet', function () {
|
|||
|
||||
it('should pass instruction into the activate hook of a controller', function () {
|
||||
var spy = jasmine.createSpy('activate');
|
||||
var UserController = registerComponent('user', '', {
|
||||
onActivate: spy
|
||||
registerComponent('userCmp', {
|
||||
$onActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/user/:name', component: UserController }
|
||||
{ path: '/user/:name', component: 'userCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/user/brian');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor(UserController), undefined);
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor('userCmp'), undefined);
|
||||
});
|
||||
|
||||
|
||||
it('should pass previous instruction into the activate hook of a controller', function () {
|
||||
var spy = jasmine.createSpy('activate');
|
||||
var activate = registerComponent('activate', '', {
|
||||
onActivate: spy
|
||||
var activate = registerComponent('activateCmp', {
|
||||
template: 'hi',
|
||||
$onActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/user/:name', component: OneController },
|
||||
{ path: '/post/:id', component: activate }
|
||||
{ path: '/user/:name', component: 'oneCmp' },
|
||||
{ path: '/post/:id', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
|
@ -92,19 +85,21 @@ describe('ngOutlet', function () {
|
|||
$rootScope.$digest();
|
||||
$router.navigateByUrl('/post/123');
|
||||
$rootScope.$digest();
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor(activate),
|
||||
instructionFor(OneController));
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor('activateCmp'),
|
||||
instructionFor('oneCmp'));
|
||||
});
|
||||
|
||||
it('should inject $scope into the controller constructor', function () {
|
||||
|
||||
var injectedScope;
|
||||
var UserController = registerComponent('user', '', function ($scope) {
|
||||
registerComponent('userCmp', {
|
||||
template: '',
|
||||
controller: function ($scope) {
|
||||
injectedScope = $scope;
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/user', component: UserController }
|
||||
{ path: '/user', component: 'userCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
|
@ -117,13 +112,13 @@ describe('ngOutlet', function () {
|
|||
|
||||
it('should run the deactivate hook of controllers', function () {
|
||||
var spy = jasmine.createSpy('deactivate');
|
||||
var deactivate = registerComponent('deactivate', '', {
|
||||
onDeactivate: spy
|
||||
registerComponent('deactivateCmp', {
|
||||
$onDeactivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: deactivate },
|
||||
{ path: '/b', component: OneController }
|
||||
{ path: '/a', component: 'deactivateCmp' },
|
||||
{ path: '/b', component: 'oneCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
|
@ -137,13 +132,13 @@ describe('ngOutlet', function () {
|
|||
|
||||
it('should pass instructions into the deactivate hook of controllers', function () {
|
||||
var spy = jasmine.createSpy('deactivate');
|
||||
var deactivate = registerComponent('deactivate', '', {
|
||||
onDeactivate: spy
|
||||
registerComponent('deactivateCmp', {
|
||||
$onDeactivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/user/:name', component: deactivate },
|
||||
{ path: '/post/:id', component: OneController }
|
||||
{ path: '/user/:name', component: 'deactivateCmp' },
|
||||
{ path: '/post/:id', component: 'oneCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
|
@ -151,29 +146,29 @@ describe('ngOutlet', function () {
|
|||
$rootScope.$digest();
|
||||
$router.navigateByUrl('/post/123');
|
||||
$rootScope.$digest();
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor(OneController),
|
||||
instructionFor(deactivate));
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor('oneCmp'),
|
||||
instructionFor('deactivateCmp'));
|
||||
});
|
||||
|
||||
|
||||
it('should run the deactivate hook before the activate hook', function () {
|
||||
var log = [];
|
||||
|
||||
var activate = registerComponent('activate', '', {
|
||||
onActivate: function () {
|
||||
registerComponent('activateCmp', {
|
||||
$onActivate: function () {
|
||||
log.push('activate');
|
||||
}
|
||||
});
|
||||
|
||||
var deactivate = registerComponent('deactivate', '', {
|
||||
onDeactivate: function () {
|
||||
registerComponent('deactivateCmp', {
|
||||
$onDeactivate: function () {
|
||||
log.push('deactivate');
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: deactivate },
|
||||
{ path: '/b', component: activate }
|
||||
{ path: '/a', component: 'deactivateCmp' },
|
||||
{ path: '/b', component: 'activateCmp' }
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
|
@ -185,25 +180,32 @@ describe('ngOutlet', function () {
|
|||
expect(log).toEqual(['deactivate', 'activate']);
|
||||
});
|
||||
|
||||
|
||||
it('should reuse a component when the canReuse hook returns true', function () {
|
||||
var log = [];
|
||||
var cmpInstanceCount = 0;
|
||||
|
||||
function ReuseCmp() {
|
||||
cmpInstanceCount++;
|
||||
this.canReuse = function () {
|
||||
return true;
|
||||
};
|
||||
this.onReuse = function (next, prev) {
|
||||
log.push('reuse: ' + prev.urlPath + ' -> ' + next.urlPath);
|
||||
};
|
||||
}
|
||||
ReuseCmp.$routeConfig = [{path: '/a', component: OneController}, {path: '/b', component: TwoController}];
|
||||
registerComponent('reuse', 'reuse {<ng-outlet></ng-outlet>}', ReuseCmp);
|
||||
|
||||
registerComponent('reuseCmp', {
|
||||
template: 'reuse {<ng-outlet></ng-outlet>}',
|
||||
$routeConfig: [
|
||||
{path: '/a', component: 'oneCmp'},
|
||||
{path: '/b', component: 'twoCmp'}
|
||||
],
|
||||
controller: ReuseCmp,
|
||||
$canReuse: function () {
|
||||
return true;
|
||||
},
|
||||
$onReuse: function (next, prev) {
|
||||
log.push('reuse: ' + prev.urlPath + ' -> ' + next.urlPath);
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/on-reuse/:number/...', component: ReuseCmp }
|
||||
{ path: '/on-reuse/:number/...', component: 'reuseCmp' },
|
||||
{ path: '/two', component: 'twoCmp', as: 'Two'}
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
|
@ -227,18 +229,25 @@ describe('ngOutlet', function () {
|
|||
|
||||
function NeverReuseCmp() {
|
||||
cmpInstanceCount++;
|
||||
this.canReuse = function () {
|
||||
return false;
|
||||
};
|
||||
this.onReuse = function (next, prev) {
|
||||
log.push('reuse: ' + prev.urlPath + ' -> ' + next.urlPath);
|
||||
};
|
||||
}
|
||||
NeverReuseCmp.$routeConfig = [{path: '/a', component: OneController}, {path: '/b', component: TwoController}];
|
||||
registerComponent('reuse', 'reuse {<ng-outlet></ng-outlet>}', NeverReuseCmp);
|
||||
registerComponent('reuseCmp', {
|
||||
template: 'reuse {<ng-outlet></ng-outlet>}',
|
||||
$routeConfig: [
|
||||
{path: '/a', component: 'oneCmp'},
|
||||
{path: '/b', component: 'twoCmp'}
|
||||
],
|
||||
controller: NeverReuseCmp,
|
||||
$canReuse: function () {
|
||||
return false;
|
||||
},
|
||||
$onReuse: function (next, prev) {
|
||||
log.push('reuse: ' + prev.urlPath + ' -> ' + next.urlPath);
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/never-reuse/:number/...', component: NeverReuseCmp }
|
||||
{ path: '/never-reuse/:number/...', component: 'reuseCmp' },
|
||||
{ path: '/two', component: 'twoCmp', as: 'Two'}
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
|
@ -256,17 +265,17 @@ describe('ngOutlet', function () {
|
|||
});
|
||||
|
||||
|
||||
// TODO: need to solve getting ahold of canActivate hook
|
||||
it('should not activate a component when canActivate returns false', function () {
|
||||
var canActivateSpy = jasmine.createSpy('canActivate').and.returnValue(false);
|
||||
var spy = jasmine.createSpy('activate');
|
||||
var activate = registerComponent('activate', '', {
|
||||
canActivate: function () {
|
||||
return false;
|
||||
},
|
||||
onActivate: spy
|
||||
registerComponent('activateCmp', {
|
||||
$canActivate: canActivateSpy,
|
||||
$onActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: activate }
|
||||
{ path: '/a', component: 'activateCmp' }
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
|
@ -279,38 +288,40 @@ describe('ngOutlet', function () {
|
|||
|
||||
|
||||
it('should activate a component when canActivate returns true', function () {
|
||||
var spy = jasmine.createSpy('activate');
|
||||
var activate = registerComponent('activate', 'hi', {
|
||||
canActivate: function () {
|
||||
return true;
|
||||
},
|
||||
onActivate: spy
|
||||
var activateSpy = jasmine.createSpy('activate');
|
||||
var canActivateSpy = jasmine.createSpy('canActivate').and.returnValue(true);
|
||||
registerComponent('activateCmp', {
|
||||
template: 'hi',
|
||||
$canActivate: canActivateSpy,
|
||||
$onActivate: activateSpy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: activate }
|
||||
{ path: '/a', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(canActivateSpy).toHaveBeenCalled();
|
||||
expect(activateSpy).toHaveBeenCalled();
|
||||
expect(elt.text()).toBe('hi');
|
||||
});
|
||||
|
||||
|
||||
it('should activate a component when canActivate returns a resolved promise', inject(function ($q) {
|
||||
var spy = jasmine.createSpy('activate');
|
||||
var activate = registerComponent('activate', 'hi', {
|
||||
canActivate: function () {
|
||||
registerComponent('activateCmp', {
|
||||
template: 'hi',
|
||||
$canActivate: function () {
|
||||
return $q.when(true);
|
||||
},
|
||||
onActivate: spy
|
||||
$onActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: activate }
|
||||
{ path: '/a', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
|
@ -324,33 +335,38 @@ describe('ngOutlet', function () {
|
|||
|
||||
it('should inject into the canActivate hook of controllers', inject(function ($http) {
|
||||
var spy = jasmine.createSpy('canActivate').and.returnValue(true);
|
||||
var activate = registerComponent('activate', '', {
|
||||
canActivate: spy
|
||||
registerComponent('activateCmp', {
|
||||
$canActivate: spy
|
||||
});
|
||||
|
||||
spy.$inject = ['$routeParams', '$http'];
|
||||
spy.$inject = ['$nextInstruction', '$http'];
|
||||
|
||||
$router.config([
|
||||
{ path: '/user/:name', component: activate }
|
||||
{ path: '/user/:name', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/user/brian');
|
||||
$rootScope.$digest();
|
||||
expect(spy).toHaveBeenCalledWith({name: 'brian'}, $http);
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
var args = spy.calls.mostRecent().args;
|
||||
expect(args[0].params).toEqual({name: 'brian'});
|
||||
expect(args[1]).toBe($http);
|
||||
}));
|
||||
|
||||
|
||||
it('should not navigate when canDeactivate returns false', function () {
|
||||
var activate = registerComponent('activate', 'hi', {
|
||||
canDeactivate: function () {
|
||||
registerComponent('activateCmp', {
|
||||
template: 'hi',
|
||||
$canDeactivate: function () {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: activate },
|
||||
{ path: '/b', component: OneController }
|
||||
{ path: '/a', component: 'activateCmp' },
|
||||
{ path: '/b', component: 'oneCmp' }
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
|
@ -365,15 +381,16 @@ describe('ngOutlet', function () {
|
|||
|
||||
|
||||
it('should navigate when canDeactivate returns true', function () {
|
||||
var activate = registerComponent('activate', 'hi', {
|
||||
canDeactivate: function () {
|
||||
registerComponent('activateCmp', {
|
||||
template: 'hi',
|
||||
$canDeactivate: function () {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: activate },
|
||||
{ path: '/b', component: OneController }
|
||||
{ path: '/a', component: 'activateCmp' },
|
||||
{ path: '/b', component: 'oneCmp' }
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
|
@ -389,15 +406,16 @@ describe('ngOutlet', function () {
|
|||
|
||||
it('should activate a component when canActivate returns true', function () {
|
||||
var spy = jasmine.createSpy('activate');
|
||||
var activate = registerComponent('activate', 'hi', {
|
||||
canActivate: function () {
|
||||
registerComponent('activateCmp', {
|
||||
template: 'hi',
|
||||
$canActivate: function () {
|
||||
return true;
|
||||
},
|
||||
onActivate: spy
|
||||
$onActivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: activate }
|
||||
{ path: '/a', component: 'activateCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
|
@ -411,13 +429,13 @@ describe('ngOutlet', function () {
|
|||
|
||||
it('should pass instructions into the canDeactivate hook of controllers', function () {
|
||||
var spy = jasmine.createSpy('canDeactivate').and.returnValue(true);
|
||||
var deactivate = registerComponent('deactivate', '', {
|
||||
canDeactivate: spy
|
||||
registerComponent('deactivateCmp', {
|
||||
$canDeactivate: spy
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/user/:name', component: deactivate },
|
||||
{ path: '/post/:id', component: OneController }
|
||||
{ path: '/user/:name', component: 'deactivateCmp' },
|
||||
{ path: '/post/:id', component: 'oneCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
|
@ -425,43 +443,36 @@ describe('ngOutlet', function () {
|
|||
$rootScope.$digest();
|
||||
$router.navigateByUrl('/post/123');
|
||||
$rootScope.$digest();
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor(OneController),
|
||||
instructionFor(deactivate));
|
||||
expect(spy).toHaveBeenCalledWith(instructionFor('oneCmp'),
|
||||
instructionFor('deactivateCmp'));
|
||||
});
|
||||
|
||||
function registerComponent(name, template, config) {
|
||||
var Ctrl;
|
||||
if (!template) {
|
||||
template = '';
|
||||
}
|
||||
if (!config) {
|
||||
Ctrl = function () {};
|
||||
} else if (angular.isArray(config)) {
|
||||
Ctrl = function () {};
|
||||
Ctrl.annotations = [new angular.annotations.RouteConfig(config)];
|
||||
} else if (typeof config === 'function') {
|
||||
Ctrl = config;
|
||||
} else {
|
||||
Ctrl = function () {};
|
||||
if (config.canActivate) {
|
||||
Ctrl.$canActivate = config.canActivate;
|
||||
delete config.canActivate;
|
||||
}
|
||||
Ctrl.prototype = config;
|
||||
}
|
||||
$controllerProvider.register(componentControllerName(name), Ctrl);
|
||||
put(name, template);
|
||||
return Ctrl;
|
||||
}
|
||||
|
||||
function boringController(model, value) {
|
||||
return function () {
|
||||
this[model] = value;
|
||||
function registerComponent(name, options) {
|
||||
var controller = options.controller || function () {};
|
||||
|
||||
['$onActivate', '$onDeactivate', '$onReuse', '$canReuse', '$canDeactivate'].forEach(function (hookName) {
|
||||
if (options[hookName]) {
|
||||
controller.prototype[hookName] = options[hookName];
|
||||
}
|
||||
});
|
||||
|
||||
function factory() {
|
||||
return {
|
||||
template: options.template || '',
|
||||
controllerAs: name,
|
||||
controller: controller
|
||||
};
|
||||
}
|
||||
|
||||
function put(name, template) {
|
||||
$templateCache.put(componentTemplatePath(name), [200, template, {}]);
|
||||
if (options.$canActivate) {
|
||||
factory.$canActivate = options.$canActivate;
|
||||
}
|
||||
if (options.$routeConfig) {
|
||||
factory.$routeConfig = options.$routeConfig;
|
||||
}
|
||||
|
||||
$compileProvider.directive(name, factory);
|
||||
}
|
||||
|
||||
function compile(template) {
|
||||
|
@ -469,4 +480,8 @@ describe('ngOutlet', function () {
|
|||
$rootScope.$digest();
|
||||
return elt;
|
||||
}
|
||||
|
||||
function instructionFor(componentType) {
|
||||
return jasmine.objectContaining({componentType: componentType});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,41 +6,39 @@ describe('navigation', function () {
|
|||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$templateCache,
|
||||
$controllerProvider,
|
||||
$componentMapperProvider;
|
||||
|
||||
var OneController, TwoController, UserController;
|
||||
|
||||
$compileProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
module('ng');
|
||||
module('ngComponentRouter');
|
||||
module(function (_$controllerProvider_, _$componentMapperProvider_) {
|
||||
$controllerProvider = _$controllerProvider_;
|
||||
$componentMapperProvider = _$componentMapperProvider_;
|
||||
module(function (_$compileProvider_) {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
|
||||
inject(function (_$compile_, _$rootScope_, _$router_, _$templateCache_) {
|
||||
inject(function (_$compile_, _$rootScope_, _$router_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
$templateCache = _$templateCache_;
|
||||
});
|
||||
|
||||
UserController = registerComponent('user', '<div>hello {{user.name}}</div>', function ($routeParams) {
|
||||
this.name = $routeParams.name;
|
||||
registerComponent('userCmp', {
|
||||
template: '<div>hello {{userCmp.$routeParams.name}}</div>'
|
||||
});
|
||||
registerComponent('oneCmp', {
|
||||
template: '<div>{{oneCmp.number}}</div>',
|
||||
controller: function () {this.number = 'one'}
|
||||
});
|
||||
registerComponent('twoCmp', {
|
||||
template: '<div>{{twoCmp.number}}</div>',
|
||||
controller: function () {this.number = 'two'}
|
||||
});
|
||||
OneController = registerComponent('one', '<div>{{one.number}}</div>', boringController('number', 'one'));
|
||||
TwoController = registerComponent('two', '<div>{{two.number}}</div>', boringController('number', 'two'));
|
||||
});
|
||||
|
||||
|
||||
it('should work in a simple case', function () {
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
$router.config([
|
||||
{ path: '/', component: OneController }
|
||||
{ path: '/', component: 'oneCmp' }
|
||||
]);
|
||||
|
||||
$router.navigateByUrl('/');
|
||||
|
@ -49,26 +47,9 @@ describe('navigation', function () {
|
|||
expect(elt.text()).toBe('one');
|
||||
});
|
||||
|
||||
|
||||
// See https://github.com/angular/router/issues/105
|
||||
xit('should warn when instantiating a component with no controller', function () {
|
||||
put('noController', '<div>{{ 2 + 2 }}</div>');
|
||||
$router.config([
|
||||
{ path: '/', component: 'noController' }
|
||||
]);
|
||||
|
||||
spyOn(console, 'warn');
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
$router.navigateByUrl('/');
|
||||
|
||||
expect(console.warn).toHaveBeenCalledWith('Could not find controller for', 'NoControllerController');
|
||||
expect(elt.text()).toBe('4');
|
||||
});
|
||||
|
||||
|
||||
it('should navigate between components with different parameters', function () {
|
||||
$router.config([
|
||||
{ path: '/user/:name', component: UserController }
|
||||
{ path: '/user/:name', component: 'userCmp' }
|
||||
]);
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
|
@ -82,42 +63,46 @@ describe('navigation', function () {
|
|||
});
|
||||
|
||||
|
||||
it('should not reactivate a parent when navigating between child components with different parameters', function () {
|
||||
var spy = jasmine.createSpy('onActivate');
|
||||
function ParentController() {}
|
||||
ParentController.$routeConfig = [
|
||||
{ path: '/user/:name', component: UserController }
|
||||
];
|
||||
ParentController.prototype.onActivate = spy;
|
||||
|
||||
registerComponent('parent', 'parent { <ng-outlet></ng-outlet> }', ParentController);
|
||||
it('should reuse a parent when navigating between child components with different parameters', function () {
|
||||
var instanceCount = 0;
|
||||
function ParentController() {
|
||||
instanceCount += 1;
|
||||
}
|
||||
registerComponent('parentCmp', {
|
||||
template: 'parent { <ng-outlet></ng-outlet> }',
|
||||
$routeConfig: [
|
||||
{ path: '/user/:name', component: 'userCmp' }
|
||||
],
|
||||
controller: ParentController
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/parent/...', component: ParentController }
|
||||
{ path: '/parent/...', component: 'parentCmp' }
|
||||
]);
|
||||
compile('<ng-outlet></ng-outlet>');
|
||||
|
||||
$router.navigateByUrl('/parent/user/brian');
|
||||
$rootScope.$digest();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(instanceCount).toBe(1);
|
||||
expect(elt.text()).toBe('parent { hello brian }');
|
||||
|
||||
spy.calls.reset();
|
||||
|
||||
$router.navigateByUrl('/parent/user/igor');
|
||||
$rootScope.$digest();
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
expect(instanceCount).toBe(1);
|
||||
expect(elt.text()).toBe('parent { hello igor }');
|
||||
});
|
||||
|
||||
|
||||
it('should work with nested outlets', function () {
|
||||
var childComponent = registerComponent('childComponent', '<div>inner { <div ng-outlet></div> }</div>', [
|
||||
{ path: '/b', component: OneController }
|
||||
]);
|
||||
registerComponent('childCmp', {
|
||||
template: '<div>inner { <div ng-outlet></div> }</div>',
|
||||
$routeConfig: [
|
||||
{ path: '/b', component: 'oneCmp' }
|
||||
]
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/a/...', component: childComponent }
|
||||
{ path: '/a/...', component: 'childCmp' }
|
||||
]);
|
||||
compile('<div>outer { <div ng-outlet></div> }</div>');
|
||||
|
||||
|
@ -128,40 +113,30 @@ describe('navigation', function () {
|
|||
});
|
||||
|
||||
|
||||
it('should work with recursive nested outlets', function () {
|
||||
put('two', '<div>recur { <div ng-outlet></div> }</div>');
|
||||
// TODO: fix this
|
||||
xit('should work with recursive nested outlets', function () {
|
||||
registerComponent('recurCmp', {
|
||||
template: '<div>recur { <div ng-outlet></div> }</div>',
|
||||
$routeConfig: [
|
||||
{ path: '/recur', component: 'recurCmp' },
|
||||
{ path: '/end', component: 'oneCmp' }
|
||||
]});
|
||||
|
||||
$router.config([
|
||||
{ path: '/recur', component: TwoController },
|
||||
{ path: '/', component: OneController }
|
||||
{ path: '/recur', component: 'recurCmp' },
|
||||
{ path: '/', component: 'oneCmp' }
|
||||
]);
|
||||
|
||||
compile('<div>root { <div ng-outlet></div> }</div>');
|
||||
$router.navigateByUrl('/');
|
||||
$router.navigateByUrl('/recur/recur/end');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('root { one }');
|
||||
});
|
||||
|
||||
it('should inject $scope into the controller constructor', function () {
|
||||
var injectedScope;
|
||||
var UserController = registerComponent('user', '', function ($scope) {
|
||||
injectedScope = $scope;
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/user', component: UserController }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/user');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(injectedScope).toBeDefined();
|
||||
});
|
||||
|
||||
|
||||
it('should change location path', inject(function ($location) {
|
||||
$router.config([
|
||||
{ path: '/user', component: UserController }
|
||||
{ path: '/user', component: 'userCmp' }
|
||||
]);
|
||||
|
||||
compile('<div ng-outlet></div>');
|
||||
|
@ -178,7 +153,7 @@ describe('navigation', function () {
|
|||
|
||||
$router.config([
|
||||
{ path: '/', redirectTo: '/user' },
|
||||
{ path: '/user', component: UserController }
|
||||
{ path: '/user', component: 'userCmp' }
|
||||
]);
|
||||
|
||||
$router.navigateByUrl('/');
|
||||
|
@ -189,16 +164,19 @@ describe('navigation', function () {
|
|||
|
||||
|
||||
it('should change location to the canonical route with nested components', inject(function ($location) {
|
||||
var childRouter = registerComponent('childRouter', '<div>inner { <div ng-outlet></div> }</div>', [
|
||||
registerComponent('childRouter', {
|
||||
template: '<div>inner { <div ng-outlet></div> }</div>',
|
||||
$routeConfig: [
|
||||
{ path: '/old-child', redirectTo: '/new-child' },
|
||||
{ path: '/new-child', component: OneController},
|
||||
{ path: '/new-child', component: 'oneCmp'},
|
||||
{ path: '/old-child-two', redirectTo: '/new-child-two' },
|
||||
{ path: '/new-child-two', component: TwoController}
|
||||
]);
|
||||
{ path: '/new-child-two', component: 'twoCmp'}
|
||||
]
|
||||
});
|
||||
|
||||
$router.config([
|
||||
{ path: '/old-parent', redirectTo: '/new-parent' },
|
||||
{ path: '/new-parent/...', component: childRouter }
|
||||
{ path: '/new-parent/...', component: 'childRouter' }
|
||||
]);
|
||||
|
||||
compile('<div ng-outlet></div>');
|
||||
|
@ -219,7 +197,7 @@ describe('navigation', function () {
|
|||
|
||||
it('should navigate when the location path changes', inject(function ($location) {
|
||||
$router.config([
|
||||
{ path: '/one', component: OneController }
|
||||
{ path: '/one', component: 'oneCmp' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
|
@ -232,18 +210,18 @@ describe('navigation', function () {
|
|||
|
||||
it('should expose a "navigating" property on $router', inject(function ($q) {
|
||||
var defer;
|
||||
var pendingActivate = registerComponent('pendingActivate', '', {
|
||||
onActivate: function () {
|
||||
registerComponent('pendingActivate', {
|
||||
$canActivate: function () {
|
||||
defer = $q.defer();
|
||||
return defer.promise;
|
||||
}
|
||||
});
|
||||
$router.config([
|
||||
{ path: '/pendingActivate', component: pendingActivate }
|
||||
{ path: '/pending-activate', component: 'pendingActivate' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/pendingActivate');
|
||||
$router.navigateByUrl('/pending-activate');
|
||||
$rootScope.$digest();
|
||||
expect($router.navigating).toBe(true);
|
||||
defer.resolve();
|
||||
|
@ -251,40 +229,31 @@ describe('navigation', function () {
|
|||
expect($router.navigating).toBe(false);
|
||||
}));
|
||||
|
||||
function registerComponent(name, options) {
|
||||
var controller = options.controller || function () {};
|
||||
|
||||
function registerComponent(name, template, config) {
|
||||
var Ctrl;
|
||||
if (!template) {
|
||||
template = '';
|
||||
}
|
||||
if (!config) {
|
||||
Ctrl = function () {};
|
||||
} else if (angular.isArray(config)) {
|
||||
Ctrl = function () {};
|
||||
Ctrl.annotations = [new angular.annotations.RouteConfig(config)];
|
||||
} else if (typeof config === 'function') {
|
||||
Ctrl = config;
|
||||
} else {
|
||||
Ctrl = function () {};
|
||||
if (config.canActivate) {
|
||||
Ctrl.$canActivate = config.canActivate;
|
||||
delete config.canActivate;
|
||||
}
|
||||
Ctrl.prototype = config;
|
||||
}
|
||||
$controllerProvider.register(componentControllerName(name), Ctrl);
|
||||
put(name, template);
|
||||
return Ctrl;
|
||||
['$onActivate', '$onDeactivate', '$onReuse', '$canReuse', '$canDeactivate'].forEach(function (hookName) {
|
||||
if (options[hookName]) {
|
||||
controller.prototype[hookName] = options[hookName];
|
||||
}
|
||||
});
|
||||
|
||||
function boringController(model, value) {
|
||||
return function () {
|
||||
this[model] = value;
|
||||
function factory() {
|
||||
return {
|
||||
template: options.template || '',
|
||||
controllerAs: name,
|
||||
controller: controller
|
||||
};
|
||||
}
|
||||
|
||||
function put(name, template) {
|
||||
$templateCache.put(componentTemplatePath(name), [200, template, {}]);
|
||||
if (options.$canActivate) {
|
||||
factory.$canActivate = options.$canActivate;
|
||||
}
|
||||
if (options.$routeConfig) {
|
||||
factory.$routeConfig = options.$routeConfig;
|
||||
}
|
||||
|
||||
$compileProvider.directive(name, factory);
|
||||
}
|
||||
|
||||
function compile(template) {
|
||||
|
|
|
@ -1,44 +1,36 @@
|
|||
'use strict';
|
||||
|
||||
describe('ngOutlet', function () {
|
||||
describe('ngLink', function () {
|
||||
|
||||
var elt,
|
||||
$compile,
|
||||
$rootScope,
|
||||
$router,
|
||||
$templateCache,
|
||||
$controllerProvider;
|
||||
|
||||
var OneController, TwoController, UserController;
|
||||
$compileProvider;
|
||||
|
||||
beforeEach(function () {
|
||||
module('ng');
|
||||
module('ngComponentRouter');
|
||||
module(function (_$controllerProvider_) {
|
||||
$controllerProvider = _$controllerProvider_;
|
||||
module(function (_$compileProvider_) {
|
||||
$compileProvider = _$compileProvider_;
|
||||
});
|
||||
|
||||
inject(function (_$compile_, _$rootScope_, _$router_, _$templateCache_) {
|
||||
inject(function (_$compile_, _$rootScope_, _$router_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$router = _$router_;
|
||||
$templateCache = _$templateCache_;
|
||||
});
|
||||
|
||||
UserController = registerComponent('user', '<div>hello {{user.name}}</div>', function ($routeParams) {
|
||||
this.name = $routeParams.name;
|
||||
});
|
||||
OneController = registerComponent('one', '<div>{{one.number}}</div>', boringController('number', 'one'));
|
||||
TwoController = registerComponent('two', '<div>{{two.number}}</div>', boringController('number', 'two'));
|
||||
registerComponent('userCmp', '<div>hello {{userCmp.$routeParams.name}}</div>', function () {});
|
||||
registerComponent('oneCmp', '<div>{{oneCmp.number}}</div>', function () {this.number = 'one'});
|
||||
registerComponent('twoCmp', '<div><a ng-link="[\'/Two\']">{{twoCmp.number}}</a></div>', function () {this.number = 'two'});
|
||||
});
|
||||
|
||||
|
||||
it('should allow linking from the parent to the child', function () {
|
||||
put('one', '<div>{{number}}</div>');
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: OneController },
|
||||
{ path: '/b', component: TwoController, as: 'Two' }
|
||||
{ path: '/a', component: 'oneCmp' },
|
||||
{ path: '/b', component: 'twoCmp', as: 'Two' }
|
||||
]);
|
||||
compile('<a ng-link="[\'/Two\']">link</a> | outer { <div ng-outlet></div> }');
|
||||
|
||||
|
@ -49,15 +41,13 @@ describe('ngOutlet', function () {
|
|||
});
|
||||
|
||||
it('should allow linking from the child and the parent', function () {
|
||||
put('one', '<div><a ng-link="[\'/Two\']">{{number}}</a></div>');
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: OneController },
|
||||
{ path: '/b', component: TwoController, as: 'Two' }
|
||||
{ path: '/a', component: 'oneCmp' },
|
||||
{ path: '/b', component: 'twoCmp', as: 'Two' }
|
||||
]);
|
||||
compile('outer { <div ng-outlet></div> }');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$router.navigateByUrl('/b');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./b');
|
||||
|
@ -65,12 +55,11 @@ describe('ngOutlet', function () {
|
|||
|
||||
|
||||
it('should allow params in routerLink directive', function () {
|
||||
put('router', '<div>outer { <div ng-outlet></div> }</div>');
|
||||
put('one', '<div><a ng-link="[\'/Two\', {param: \'lol\'}]">{{number}}</a></div>');
|
||||
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: \'lol\'}]">{{twoLinkCmp.number}}</a></div>', function () {this.number = 'two'});
|
||||
|
||||
$router.config([
|
||||
{ path: '/a', component: OneController },
|
||||
{ path: '/b/:param', component: TwoController, as: 'Two' }
|
||||
{ path: '/a', component: 'twoLinkCmp' },
|
||||
{ path: '/b/:param', component: 'twoCmp', as: 'Two' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
|
@ -80,33 +69,34 @@ describe('ngOutlet', function () {
|
|||
expect(elt.find('a').attr('href')).toBe('./b/lol');
|
||||
});
|
||||
|
||||
// TODO: test dynamic links
|
||||
it('should update the href of links with bound params', function () {
|
||||
put('router', '<div>outer { <div ng-outlet></div> }</div>');
|
||||
put('one', '<div><a ng-link="[\'/Two\', {param: one.number}]">{{one.number}}</a></div>');
|
||||
|
||||
it('should update the href of links with bound params', function () {
|
||||
registerComponent('twoLinkCmp', '<div><a ng-link="[\'/Two\', {param: twoLinkCmp.number}]">{{twoLinkCmp.number}}</a></div>', function () {this.number = 'param'});
|
||||
$router.config([
|
||||
{ path: '/a', component: OneController },
|
||||
{ path: '/b/:param', component: TwoController, as: 'Two' }
|
||||
{ path: '/a', component: 'twoLinkCmp' },
|
||||
{ path: '/b/:param', component: 'twoCmp', as: 'Two' }
|
||||
]);
|
||||
compile('<div ng-outlet></div>');
|
||||
|
||||
$router.navigateByUrl('/a');
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./b/one');
|
||||
expect(elt.find('a').attr('href')).toBe('./b/param');
|
||||
});
|
||||
|
||||
|
||||
it('should navigate on left-mouse click when a link url matches a route', function () {
|
||||
$router.config([
|
||||
{ path: '/', component: OneController },
|
||||
{ path: '/two', component: TwoController }
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', as: 'Two'}
|
||||
]);
|
||||
|
||||
compile('<a href="/two">link</a> | <div ng-outlet></div>');
|
||||
compile('<a ng-link="[\'/Two\']">link</a> | <div ng-outlet></div>');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('link | one');
|
||||
|
||||
expect(elt.find('a').attr('href')).toBe('./two');
|
||||
|
||||
elt.find('a')[0].click();
|
||||
|
||||
$rootScope.$digest();
|
||||
|
@ -116,11 +106,11 @@ describe('ngOutlet', function () {
|
|||
|
||||
it('should not navigate on non-left mouse click when a link url matches a route', inject(function ($router) {
|
||||
$router.config([
|
||||
{ path: '/', component: OneController },
|
||||
{ path: '/two', component: TwoController }
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', as: 'Two'}
|
||||
]);
|
||||
|
||||
compile('<a href="./two">link</a> | <div ng-outlet></div>');
|
||||
compile('<a ng-link="[\'/Two\']">link</a> | <div ng-outlet></div>');
|
||||
$rootScope.$digest();
|
||||
expect(elt.text()).toBe('link | one');
|
||||
elt.find('a').triggerHandler({ type: 'click', which: 3 });
|
||||
|
@ -133,8 +123,8 @@ describe('ngOutlet', function () {
|
|||
// See https://github.com/angular/router/issues/206
|
||||
it('should not navigate a link without an href', function () {
|
||||
$router.config([
|
||||
{ path: '/', component: OneController },
|
||||
{ path: '/two', component: TwoController }
|
||||
{ path: '/', component: 'oneCmp' },
|
||||
{ path: '/two', component: 'twoCmp', as: 'Two'}
|
||||
]);
|
||||
expect(function () {
|
||||
compile('<a>link</a>');
|
||||
|
@ -147,38 +137,29 @@ describe('ngOutlet', function () {
|
|||
|
||||
|
||||
function registerComponent(name, template, config) {
|
||||
var Ctrl;
|
||||
if (!template) {
|
||||
template = '';
|
||||
}
|
||||
if (!config) {
|
||||
Ctrl = function () {};
|
||||
} else if (angular.isArray(config)) {
|
||||
Ctrl = function () {};
|
||||
Ctrl.annotations = [new angular.annotations.RouteConfig(config)];
|
||||
} else if (typeof config === 'function') {
|
||||
Ctrl = config;
|
||||
} else {
|
||||
Ctrl = function () {};
|
||||
if (config.canActivate) {
|
||||
Ctrl.$canActivate = config.canActivate;
|
||||
delete config.canActivate;
|
||||
}
|
||||
Ctrl.prototype = config;
|
||||
}
|
||||
$controllerProvider.register(componentControllerName(name), Ctrl);
|
||||
put(name, template);
|
||||
return Ctrl;
|
||||
}
|
||||
var controller = function () {};
|
||||
|
||||
function boringController(model, value) {
|
||||
return function () {
|
||||
this[model] = value;
|
||||
function factory() {
|
||||
return {
|
||||
template: template,
|
||||
controllerAs: name,
|
||||
controller: controller
|
||||
};
|
||||
}
|
||||
|
||||
function put(name, template) {
|
||||
$templateCache.put(componentTemplatePath(name), [200, template, {}]);
|
||||
if (!template) {
|
||||
template = '';
|
||||
}
|
||||
if (angular.isArray(config)) {
|
||||
factory.annotations = [new angular.annotations.RouteConfig(config)];
|
||||
} else if (typeof config === 'function') {
|
||||
controller = config;
|
||||
} else if (typeof config === 'object') {
|
||||
if (config.canActivate) {
|
||||
controller.$canActivate = config.canActivate;
|
||||
}
|
||||
}
|
||||
$compileProvider.directive(name, factory);
|
||||
}
|
||||
|
||||
function compile(template) {
|
||||
|
|
|
@ -16,11 +16,6 @@ function dashCase(str) {
|
|||
});
|
||||
}
|
||||
|
||||
function boringController (model, value) {
|
||||
return function () {
|
||||
this[model] = value;
|
||||
};
|
||||
}
|
||||
|
||||
function provideHelpers(fn, preInject) {
|
||||
return function () {
|
||||
|
|
Loading…
Reference in New Issue