feat(UpgradeComponent): add support for `require`
This commit also adds/improves/fixes some `UpgradeComponent` tests.
This commit is contained in:
parent
469010ea8e
commit
fe1d0e29c5
|
@ -12,6 +12,8 @@ export interface IAnnotatedFunction extends Function { $inject?: Ng1Token[]; }
|
||||||
|
|
||||||
export type IInjectable = (Ng1Token | Function)[] | IAnnotatedFunction;
|
export type IInjectable = (Ng1Token | Function)[] | IAnnotatedFunction;
|
||||||
|
|
||||||
|
export type SingleOrListOrMap<T> = T | T[] | {[key: string]: T};
|
||||||
|
|
||||||
export interface IModule {
|
export interface IModule {
|
||||||
name: string;
|
name: string;
|
||||||
requires: (string|IInjectable)[];
|
requires: (string|IInjectable)[];
|
||||||
|
@ -44,6 +46,7 @@ export interface IRootScopeService {
|
||||||
$apply(): any;
|
$apply(): any;
|
||||||
$apply(exp: string): any;
|
$apply(exp: string): any;
|
||||||
$apply(exp: Function): any;
|
$apply(exp: Function): any;
|
||||||
|
$digest(): any;
|
||||||
$evalAsync(): any;
|
$evalAsync(): any;
|
||||||
$on(event: string, fn?: (event?: any, ...args: any[]) => void): Function;
|
$on(event: string, fn?: (event?: any, ...args: any[]) => void): Function;
|
||||||
$$childTail: IScope;
|
$$childTail: IScope;
|
||||||
|
@ -72,7 +75,7 @@ export interface IDirective {
|
||||||
terminal?: boolean;
|
terminal?: boolean;
|
||||||
transclude?: boolean|'element'|{[key: string]: string};
|
transclude?: boolean|'element'|{[key: string]: string};
|
||||||
}
|
}
|
||||||
export type DirectiveRequireProperty = Ng1Token[] | Ng1Token | {[key: string]: Ng1Token};
|
export type DirectiveRequireProperty = SingleOrListOrMap<string>;
|
||||||
export interface IDirectiveCompileFn {
|
export interface IDirectiveCompileFn {
|
||||||
(templateElement: IAugmentedJQuery, templateAttributes: IAttributes,
|
(templateElement: IAugmentedJQuery, templateAttributes: IAttributes,
|
||||||
transclude: ITranscludeFunction): IDirectivePrePost;
|
transclude: ITranscludeFunction): IDirectivePrePost;
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const INJECTOR_KEY = '$$angularInjector';
|
||||||
|
|
||||||
export const $INJECTOR = '$injector';
|
export const $INJECTOR = '$injector';
|
||||||
export const $PARSE = '$parse';
|
export const $PARSE = '$parse';
|
||||||
|
export const $ROOT_SCOPE = '$rootScope';
|
||||||
export const $SCOPE = '$scope';
|
export const $SCOPE = '$scope';
|
||||||
|
|
||||||
export const $COMPILE = '$compile';
|
export const $COMPILE = '$compile';
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {controllerKey} from '../util';
|
||||||
|
|
||||||
import {$COMPILE, $CONTROLLER, $HTTP_BACKEND, $INJECTOR, $SCOPE, $TEMPLATE_CACHE} from './constants';
|
import {$COMPILE, $CONTROLLER, $HTTP_BACKEND, $INJECTOR, $SCOPE, $TEMPLATE_CACHE} from './constants';
|
||||||
|
|
||||||
|
const REQUIRE_PREFIX_RE = /^(\^\^?)?(\?)?(\^\^?)?/;
|
||||||
const NOT_SUPPORTED: any = 'NOT_SUPPORTED';
|
const NOT_SUPPORTED: any = 'NOT_SUPPORTED';
|
||||||
const INITIAL_VALUE = {
|
const INITIAL_VALUE = {
|
||||||
__UNINITIALIZED__: true
|
__UNINITIALIZED__: true
|
||||||
|
@ -101,7 +102,16 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
const attrs: angular.IAttributes = NOT_SUPPORTED;
|
const attrs: angular.IAttributes = NOT_SUPPORTED;
|
||||||
const transcludeFn: angular.ITranscludeFunction = NOT_SUPPORTED;
|
const transcludeFn: angular.ITranscludeFunction = NOT_SUPPORTED;
|
||||||
const linkController = this.resolveRequired(this.$element, this.directive.require);
|
const directiveRequire = this.getDirectiveRequire(this.directive);
|
||||||
|
let requiredControllers =
|
||||||
|
this.resolveRequire(this.directive.name, this.$element, directiveRequire);
|
||||||
|
|
||||||
|
if (this.directive.bindToController && isMap(directiveRequire)) {
|
||||||
|
const requiredControllersMap = requiredControllers as{[key: string]: IControllerInstance};
|
||||||
|
Object.keys(requiredControllersMap).forEach(key => {
|
||||||
|
this.controllerInstance[key] = requiredControllersMap[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.callLifecycleHook('$onInit', this.controllerInstance);
|
this.callLifecycleHook('$onInit', this.controllerInstance);
|
||||||
|
|
||||||
|
@ -109,7 +119,7 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||||
const preLink = (typeof link == 'object') && (link as angular.IDirectivePrePost).pre;
|
const preLink = (typeof link == 'object') && (link as angular.IDirectivePrePost).pre;
|
||||||
const postLink = (typeof link == 'object') ? (link as angular.IDirectivePrePost).post : link;
|
const postLink = (typeof link == 'object') ? (link as angular.IDirectivePrePost).post : link;
|
||||||
if (preLink) {
|
if (preLink) {
|
||||||
preLink(this.$componentScope, this.$element, attrs, linkController, transcludeFn);
|
preLink(this.$componentScope, this.$element, attrs, requiredControllers, transcludeFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
var childNodes: Node[] = [];
|
var childNodes: Node[] = [];
|
||||||
|
@ -126,7 +136,7 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||||
this.linkFn(this.$componentScope, attachElement, {parentBoundTranscludeFn: attachChildNodes});
|
this.linkFn(this.$componentScope, attachElement, {parentBoundTranscludeFn: attachChildNodes});
|
||||||
|
|
||||||
if (postLink) {
|
if (postLink) {
|
||||||
postLink(this.$componentScope, this.$element, attrs, linkController, transcludeFn);
|
postLink(this.$componentScope, this.$element, attrs, requiredControllers, transcludeFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.callLifecycleHook('$postLink', this.controllerInstance);
|
this.callLifecycleHook('$postLink', this.controllerInstance);
|
||||||
|
@ -187,6 +197,24 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||||
return directive;
|
return directive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getDirectiveRequire(directive: angular.IDirective): angular.DirectiveRequireProperty {
|
||||||
|
const require = directive.require || (directive.controller && directive.name);
|
||||||
|
|
||||||
|
if (isMap(require)) {
|
||||||
|
Object.keys(require).forEach(key => {
|
||||||
|
const value = require[key];
|
||||||
|
const match = value.match(REQUIRE_PREFIX_RE);
|
||||||
|
const name = value.substring(match[0].length);
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
require[key] = match[0] + key;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return require;
|
||||||
|
}
|
||||||
|
|
||||||
private initializeBindings(directive: angular.IDirective) {
|
private initializeBindings(directive: angular.IDirective) {
|
||||||
const btcIsObject = typeof directive.bindToController === 'object';
|
const btcIsObject = typeof directive.bindToController === 'object';
|
||||||
if (btcIsObject && Object.keys(directive.scope).length) {
|
if (btcIsObject && Object.keys(directive.scope).length) {
|
||||||
|
@ -266,9 +294,47 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveRequired(
|
private resolveRequire(
|
||||||
$element: angular.IAugmentedJQuery, require: angular.DirectiveRequireProperty) {
|
directiveName: string, $element: angular.IAugmentedJQuery,
|
||||||
// TODO
|
require: angular.DirectiveRequireProperty): angular.SingleOrListOrMap<IControllerInstance> {
|
||||||
|
if (!require) {
|
||||||
|
return null;
|
||||||
|
} else if (Array.isArray(require)) {
|
||||||
|
return require.map(req => this.resolveRequire(directiveName, $element, req));
|
||||||
|
} else if (typeof require === 'object') {
|
||||||
|
const value: {[key: string]: IControllerInstance} = {};
|
||||||
|
|
||||||
|
Object.keys(require).forEach(
|
||||||
|
key => value[key] = this.resolveRequire(directiveName, $element, require[key]));
|
||||||
|
|
||||||
|
return value;
|
||||||
|
} else if (typeof require === 'string') {
|
||||||
|
const match = require.match(REQUIRE_PREFIX_RE);
|
||||||
|
const inheritType = match[1] || match[3];
|
||||||
|
|
||||||
|
const name = require.substring(match[0].length);
|
||||||
|
const isOptional = !!match[2];
|
||||||
|
const searchParents = !!inheritType;
|
||||||
|
const startOnParent = inheritType === '^^';
|
||||||
|
|
||||||
|
const ctrlKey = controllerKey(name);
|
||||||
|
|
||||||
|
if (startOnParent) {
|
||||||
|
$element = $element.parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = searchParents ? $element.inheritedData(ctrlKey) : $element.data(ctrlKey);
|
||||||
|
|
||||||
|
if (!value && !isOptional) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to find required '${require}' in upgraded directive '${directiveName}'.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Unrecognized require syntax on upgraded directive '${directiveName}': ${require}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupOutputs() {
|
private setupOutputs() {
|
||||||
|
@ -305,3 +371,8 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
|
||||||
function getOrCall<T>(property: Function | T): T {
|
function getOrCall<T>(property: Function | T): T {
|
||||||
return typeof(property) === 'function' ? property() : property;
|
return typeof(property) === 'function' ? property() : property;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Only works for `typeof T !== 'object'`.
|
||||||
|
function isMap<T>(value: angular.SingleOrListOrMap<T>): value is {[key: string]: T} {
|
||||||
|
return value && !Array.isArray(value) && typeof value === 'object';
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
import {PlatformRef, Type} from '@angular/core';
|
import {PlatformRef, Type} from '@angular/core';
|
||||||
import * as angular from '@angular/upgrade/src/angular_js';
|
import * as angular from '@angular/upgrade/src/angular_js';
|
||||||
|
import {$ROOT_SCOPE} from '@angular/upgrade/src/aot/constants';
|
||||||
import {UpgradeModule} from '@angular/upgrade/static';
|
import {UpgradeModule} from '@angular/upgrade/static';
|
||||||
|
|
||||||
export function bootstrap(
|
export function bootstrap(
|
||||||
|
@ -20,6 +21,11 @@ export function bootstrap(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function digest(adapter: UpgradeModule) {
|
||||||
|
const $rootScope = adapter.$injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
||||||
|
$rootScope.$digest();
|
||||||
|
}
|
||||||
|
|
||||||
export function html(html: string): Element {
|
export function html(html: string): Element {
|
||||||
// Don't return `body` itself, because using it as a `$rootElement` for ng1
|
// Don't return `body` itself, because using it as a `$rootElement` for ng1
|
||||||
// will attach `$injector` to it and that will affect subsequent tests.
|
// will attach `$injector` to it and that will affect subsequent tests.
|
||||||
|
|
Loading…
Reference in New Issue