
The directiveIntrospector was a bit of a hack to allow the router to read the `$routeConfig` annocation and `$routerCanActivate` hook from directives when they were registered. It turns out that if we put these properties on the component controller's constructor function (i.e. as static class methods) then we can simply use the `$injector` to access it as required. Currently, people put the properties directly on their component definition objects. In Angular 1.5.1, we will copy these properties onto the controller constructor to maintain a simple migration path. But going forward it may be better to encourage people to add the properties directly to the controller constructor.
261 lines
7.9 KiB
TypeScript
261 lines
7.9 KiB
TypeScript
///<reference path="../typings/angularjs/angular.d.ts"/>
|
|
|
|
|
|
|
|
/**
|
|
* @name ngOutlet
|
|
*
|
|
* @description
|
|
* An ngOutlet is where resolved content goes.
|
|
*
|
|
* ## Use
|
|
*
|
|
* ```html
|
|
* <div ng-outlet="name"></div>
|
|
* ```
|
|
*
|
|
* The value for the `ngOutlet` attribute is optional.
|
|
*/
|
|
function ngOutletDirective($animate, $q: ng.IQService, $rootRouter) {
|
|
let rootRouter = $rootRouter;
|
|
|
|
return {
|
|
restrict: 'AE',
|
|
transclude: 'element',
|
|
terminal: true,
|
|
priority: 400,
|
|
require: ['?^^ngOutlet', 'ngOutlet'],
|
|
link: outletLink,
|
|
controller: class {},
|
|
controllerAs: '$$ngOutlet'
|
|
};
|
|
|
|
function outletLink(scope, element, attrs, ctrls, $transclude) {
|
|
class Outlet {
|
|
constructor(private controller, private router) {}
|
|
|
|
private currentController;
|
|
private currentInstruction;
|
|
private currentScope;
|
|
private currentElement;
|
|
private previousLeaveAnimation;
|
|
|
|
private cleanupLastView() {
|
|
if (this.previousLeaveAnimation) {
|
|
$animate.cancel(this.previousLeaveAnimation);
|
|
this.previousLeaveAnimation = null;
|
|
}
|
|
|
|
if (this.currentScope) {
|
|
this.currentScope.$destroy();
|
|
this.currentScope = null;
|
|
}
|
|
if (this.currentElement) {
|
|
this.previousLeaveAnimation = $animate.leave(this.currentElement);
|
|
this.previousLeaveAnimation.then(() => this.previousLeaveAnimation = null);
|
|
this.currentElement = null;
|
|
}
|
|
}
|
|
|
|
reuse(instruction) {
|
|
let next = $q.when(true);
|
|
let previousInstruction = this.currentInstruction;
|
|
this.currentInstruction = instruction;
|
|
if (this.currentController && this.currentController.$routerOnReuse) {
|
|
next = $q.when(
|
|
this.currentController.$routerOnReuse(this.currentInstruction, previousInstruction));
|
|
}
|
|
|
|
return next;
|
|
}
|
|
|
|
routerCanReuse(nextInstruction) {
|
|
let result;
|
|
if (!this.currentInstruction ||
|
|
this.currentInstruction.componentType !== nextInstruction.componentType) {
|
|
result = false;
|
|
} else if (this.currentController && this.currentController.$routerCanReuse) {
|
|
result = this.currentController.$routerCanReuse(nextInstruction, this.currentInstruction);
|
|
} else {
|
|
result = nextInstruction === this.currentInstruction ||
|
|
angular.equals(nextInstruction.params, this.currentInstruction.params);
|
|
}
|
|
return $q.when(result);
|
|
}
|
|
|
|
routerCanDeactivate(instruction) {
|
|
if (this.currentController && this.currentController.$routerCanDeactivate) {
|
|
return $q.when(
|
|
this.currentController.$routerCanDeactivate(instruction, this.currentInstruction));
|
|
}
|
|
return $q.when(true);
|
|
}
|
|
|
|
deactivate(instruction) {
|
|
if (this.currentController && this.currentController.$routerOnDeactivate) {
|
|
return $q.when(
|
|
this.currentController.$routerOnDeactivate(instruction, this.currentInstruction));
|
|
}
|
|
return $q.when();
|
|
}
|
|
|
|
activate(instruction) {
|
|
this.previousInstruction = this.currentInstruction;
|
|
this.currentInstruction = instruction;
|
|
|
|
let componentName = this.controller.$$componentName = instruction.componentType;
|
|
|
|
if (typeof componentName !== 'string') {
|
|
throw new Error('Component is not a string for ' + instruction.urlPath);
|
|
}
|
|
|
|
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);
|
|
this.cleanupLastView();
|
|
});
|
|
|
|
this.currentElement = clone;
|
|
this.currentScope = newScope;
|
|
return this.deferredActivation.promise;
|
|
}
|
|
}
|
|
|
|
let parentCtrl = ctrls[0], myCtrl = ctrls[1],
|
|
router = (parentCtrl && parentCtrl.$$router) || rootRouter;
|
|
|
|
myCtrl.$$currentComponent = null;
|
|
|
|
router.registerPrimaryOutlet(new Outlet(myCtrl, router));
|
|
}
|
|
}
|
|
/**
|
|
* This directive is responsible for compiling the contents of ng-outlet
|
|
*/
|
|
function ngOutletFillContentDirective($compile) {
|
|
return {
|
|
restrict: 'EA',
|
|
priority: -400,
|
|
require: 'ngOutlet',
|
|
link: (scope, element, attrs, ctrl) => {
|
|
let template = ctrl.$$template;
|
|
element.html(template);
|
|
$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
|
|
* Lets you link to different parts of the app, and automatically generates hrefs.
|
|
*
|
|
* ## Use
|
|
* The directive uses a simple syntax: `ng-link="componentName({ param: paramValue })"`
|
|
*
|
|
* ### Example
|
|
*
|
|
* ```js
|
|
* angular.module('myApp', ['ngComponentRouter'])
|
|
* .controller('AppController', ['$rootRouter', function($rootRouter) {
|
|
* $rootRouter.config({ path: '/user/:id', component: 'user' });
|
|
* this.user = { name: 'Brian', id: 123 };
|
|
* });
|
|
* ```
|
|
*
|
|
* ```html
|
|
* <div ng-controller="AppController as app">
|
|
* <a ng-link="user({id: app.user.id})">{{app.user.name}}</a>
|
|
* </div>
|
|
* ```
|
|
*/
|
|
function ngLinkDirective($rootRouter, $parse) {
|
|
return {require: '?^^ngOutlet', restrict: 'A', link: ngLinkDirectiveLinkFn};
|
|
|
|
function ngLinkDirectiveLinkFn(scope, element, attrs, ctrl) {
|
|
let router = (ctrl && ctrl.$$router) || $rootRouter;
|
|
if (!router) {
|
|
return;
|
|
}
|
|
|
|
let instruction = null;
|
|
let link = attrs.ngLink || '';
|
|
|
|
function getLink(params) {
|
|
instruction = router.generate(params);
|
|
|
|
scope.$watch(function() { return router.isRouteActive(instruction); },
|
|
function(active) {
|
|
if (active) {
|
|
element.addClass('ng-link-active');
|
|
} else {
|
|
element.removeClass('ng-link-active');
|
|
}
|
|
});
|
|
|
|
return './' + angular.stringifyInstruction(instruction);
|
|
}
|
|
|
|
let routeParamsGetter = $parse(link);
|
|
// we can avoid adding a watcher if it's a literal
|
|
if (routeParamsGetter.constant) {
|
|
let params = routeParamsGetter();
|
|
element.attr('href', getLink(params));
|
|
} else {
|
|
scope.$watch(() => routeParamsGetter(scope), params => element.attr('href', getLink(params)),
|
|
true);
|
|
}
|
|
|
|
element.on('click', event => {
|
|
if (event.which !== 1 || !instruction) {
|
|
return;
|
|
}
|
|
|
|
$rootRouter.navigateByInstruction(instruction);
|
|
event.preventDefault();
|
|
});
|
|
}
|
|
}
|
|
|
|
function dashCase(str: string): string {
|
|
return str.replace(/[A-Z]/g, match => '-' + match.toLowerCase());
|
|
}
|
|
|
|
/*
|
|
* A module for adding new a routing system Angular 1.
|
|
*/
|
|
angular.module('ngComponentRouter', [])
|
|
.directive('ngOutlet', ['$animate', '$q', '$rootRouter', ngOutletDirective])
|
|
.directive('ngOutlet', ['$compile', ngOutletFillContentDirective])
|
|
.directive('ngLink', ['$rootRouter', '$parse', ngLinkDirective])
|
|
.directive('$router', ['$q', routerTriggerDirective]);
|