fix(angular1_router): support templateUrl components

This commit is contained in:
Peter Bacon Darwin 2016-02-10 16:59:26 -08:00 committed by Pete Bacon Darwin
parent e7470d557d
commit d4a4d81173
5 changed files with 82 additions and 51 deletions

View File

@ -145,7 +145,7 @@ function ngOutletDirective($animate, $q: ng.IQService, $router) {
}
activate(instruction) {
let previousInstruction = this.currentInstruction;
this.previousInstruction = this.currentInstruction;
this.currentInstruction = instruction;
let componentName = this.controller.$$componentName = instruction.componentType;
@ -154,13 +154,14 @@ function ngOutletDirective($animate, $q: ng.IQService, $router) {
throw new Error('Component is not a string for ' + instruction.urlPath);
}
this.controller.$$routeParams = instruction.params;
this.controller.$$template =
'<' + dashCase(componentName) + ' router="$$router"></' + dashCase(componentName) + '>';
this.controller.$$router = this.router.childRouter(instruction.componentType);
this.controller.$$outlet = this;
let newScope = scope.$new();
newScope.$$router = this.controller.$$router;
this.deferredActivation = $q.defer();
let clone = $transclude(newScope, clone => {
$animate.enter(clone, null, this.currentElement || element);
@ -169,15 +170,7 @@ function ngOutletDirective($animate, $q: ng.IQService, $router) {
this.currentElement = clone;
this.currentScope = newScope;
// TODO: prefer the other directive retrieving the controller
// by debug mode
this.currentController = this.currentElement.children().eq(0).controller(componentName);
if (this.currentController && this.currentController.$routerOnActivate) {
return this.currentController.$routerOnActivate(instruction, previousInstruction);
}
return $q.when();
return this.deferredActivation.promise;
}
}
@ -200,21 +193,32 @@ function ngOutletFillContentDirective($compile) {
link: (scope, element, attrs, ctrl) => {
let template = ctrl.$$template;
element.html(template);
let link = $compile(element.contents());
link(scope);
// TODO: move to primary directive
let componentInstance = scope[ctrl.$$componentName];
if (componentInstance) {
ctrl.$$currentComponent = componentInstance;
componentInstance.$router = ctrl.$$router;
componentInstance.$routeParams = ctrl.$$routeParams;
}
$compile(element.contents())(scope);
}
};
}
function routerTriggerDirective($q) {
return {
require: '^ngOutlet',
priority: -1000,
link: function(scope, element, attr, ngOutletCtrl) {
var promise = $q.when();
var outlet = ngOutletCtrl.$$outlet;
var currentComponent = outlet.currentController =
element.controller(ngOutletCtrl.$$componentName);
if (currentComponent.$routerOnActivate) {
promise = $q.when(currentComponent.$routerOnActivate(outlet.currentInstruction,
outlet.previousInstruction));
}
promise.then(outlet.deferredActivation.resolve, outlet.deferredActivation.reject);
}
};
}
/**
* @name ngLink
* @description
@ -289,7 +293,8 @@ function dashCase(str: string): string {
angular.module('ngComponentRouter', [])
.directive('ngOutlet', ['$animate', '$q', '$router', ngOutletDirective])
.directive('ngOutlet', ['$compile', ngOutletFillContentDirective])
.directive('ngLink', ['$router', '$parse', ngLinkDirective]);
.directive('ngLink', ['$router', '$parse', ngLinkDirective])
.directive('router', ['$q', routerTriggerDirective]);
/*
* A module for inspecting controller constructors

View File

@ -116,53 +116,41 @@
console.warn('Route for "' + path + '" should use "controllerAs".');
}
var directiveName = routeObjToRouteName(routeCopy, path);
var componentName = routeObjToRouteName(routeCopy, path);
if (!directiveName) {
if (!componentName) {
throw new Error('Could not determine a name for route "' + path + '".');
}
routeDefinition.component = directiveName;
routeDefinition.name = route.name || upperCase(directiveName);
routeDefinition.component = componentName;
routeDefinition.name = route.name || upperCase(componentName);
var directiveController = routeCopy.controller;
var directiveDefinition = {
scope: false,
var componentDefinition = {
controller: directiveController,
controllerAs: routeCopy.controllerAs,
templateUrl: routeCopy.templateUrl,
template: routeCopy.template
};
controllerAs: routeCopy.controllerAs
var directiveFactory = function () {
return directiveDefinition;
};
if (routeCopy.templateUrl) componentDefinition.templateUrl = routeCopy.templateUrl;
if (routeCopy.template) componentDefinition.template = routeCopy.template;
// if we have route resolve options, prepare a wrapper controller
if (directiveController && routeCopy.resolve) {
var originalController = directiveController;
var resolvedLocals = {};
directiveDefinition.controller = ['$injector', '$scope', function ($injector, $scope) {
componentDefinition.controller = ['$injector', '$scope', function ($injector, $scope) {
var locals = angular.extend({
$scope: $scope
}, resolvedLocals);
var ctrl = $injector.instantiate(originalController, locals);
if (routeCopy.controllerAs) {
locals.$scope[routeCopy.controllerAs] = ctrl;
}
return ctrl;
return $injector.instantiate(originalController, locals);
}];
// we take care of controllerAs in the directive controller wrapper
delete directiveDefinition.controllerAs;
// we resolve the locals in a canActivate block
directiveFactory.$canActivate = function() {
componentDefinition.$canActivate = function() {
var locals = angular.extend({}, routeCopy.resolve);
angular.forEach(locals, function(value, key) {
@ -179,7 +167,7 @@
}
// register the dynamically created directive
$compileProvider.directive(directiveName, directiveFactory);
$compileProvider.component(componentName, componentDefinition);
}
if (subscriptionFn) {
subscriptionFn(routeDefinition);

View File

@ -25,7 +25,10 @@ describe('ngOutlet animations', function () {
});
registerComponent('userCmp', {
template: '<div>hello {{userCmp.$routeParams.name}}</div>'
template: '<div>hello {{userCmp.$routeParams.name}}</div>',
$routerOnActivate: function(next) {
this.$routeParams = next.params;
}
});
});

View File

@ -22,7 +22,10 @@ describe('navigation', function () {
});
registerDirective('userCmp', {
template: '<div>hello {{userCmp.$routeParams.name}}</div>'
template: '<div>hello {{userCmp.$routeParams.name}}</div>',
$routerOnActivate: function(next) {
this.$routeParams = next.params;
}
});
registerDirective('oneCmp', {
template: '<div>{{oneCmp.number}}</div>',

View File

@ -95,6 +95,32 @@ describe('router', function () {
expect(elt.text()).toBe('Home (true)');
}));
it('should work with a templateUrl component', inject(function($location, $httpBackend) {
var $routerOnActivate = jasmine.createSpy('$routerOnActivate');
$httpBackend.expectGET('homeCmp.html').respond('Home');
registerComponent('homeCmp', {
templateUrl: 'homeCmp.html',
$routerOnActivate: $routerOnActivate
});
registerComponent('app', {
template: '<div ng-outlet></div>',
$routeConfig: [
{ path: '/', component: 'homeCmp' }
]
});
compile('<app></app>');
$location.path('/');
$rootScope.$digest();
$httpBackend.flush();
var homeElement = elt.find('home-cmp');
expect(homeElement.text()).toBe('Home');
expect($routerOnActivate).toHaveBeenCalled();
}));
function registerDirective(name, options) {
function factory() {
return {
@ -111,9 +137,15 @@ describe('router', function () {
var definition = {
bindings: options.bindings,
template: options.template || '',
controller: getController(options),
};
if (options.template) {
definition.template = options.template;
}
if (options.templateUrl) {
definition.templateUrl = options.templateUrl;
}
applyStaticProperties(definition, options);
$compileProvider.component(name, definition);
}