refactor: move angular source to /packages rather than modules/@angular
This commit is contained in:
234
packages/upgrade/src/common/angular1.ts
Normal file
234
packages/upgrade/src/common/angular1.ts
Normal file
@ -0,0 +1,234 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export type Ng1Token = string;
|
||||
|
||||
export type Ng1Expression = string | Function;
|
||||
|
||||
export interface IAnnotatedFunction extends Function { $inject?: Ng1Token[]; }
|
||||
|
||||
export type IInjectable = (Ng1Token | Function)[] | IAnnotatedFunction;
|
||||
|
||||
export type SingleOrListOrMap<T> = T | T[] | {[key: string]: T};
|
||||
|
||||
export interface IModule {
|
||||
name: string;
|
||||
requires: (string|IInjectable)[];
|
||||
config(fn: IInjectable): IModule;
|
||||
directive(selector: string, factory: IInjectable): IModule;
|
||||
component(selector: string, component: IComponent): IModule;
|
||||
controller(name: string, type: IInjectable): IModule;
|
||||
factory(key: Ng1Token, factoryFn: IInjectable): IModule;
|
||||
value(key: Ng1Token, value: any): IModule;
|
||||
constant(token: Ng1Token, value: any): IModule;
|
||||
run(a: IInjectable): IModule;
|
||||
}
|
||||
export interface ICompileService {
|
||||
(element: Element|NodeList|Node[]|string, transclude?: Function): ILinkFn;
|
||||
}
|
||||
export interface ILinkFn {
|
||||
(scope: IScope, cloneAttachFn?: ICloneAttachFunction, options?: ILinkFnOptions): IAugmentedJQuery;
|
||||
}
|
||||
export interface ILinkFnOptions {
|
||||
parentBoundTranscludeFn?: Function;
|
||||
transcludeControllers?: {[key: string]: any};
|
||||
futureParentElement?: Node;
|
||||
}
|
||||
export interface IRootScopeService {
|
||||
$new(isolate?: boolean): IScope;
|
||||
$id: string;
|
||||
$parent: IScope;
|
||||
$root: IScope;
|
||||
$watch(exp: Ng1Expression, fn?: (a1?: any, a2?: any) => void): Function;
|
||||
$on(event: string, fn?: (event?: any, ...args: any[]) => void): Function;
|
||||
$destroy(): any;
|
||||
$apply(exp?: Ng1Expression): any;
|
||||
$digest(): any;
|
||||
$evalAsync(): any;
|
||||
$on(event: string, fn?: (event?: any, ...args: any[]) => void): Function;
|
||||
$$childTail: IScope;
|
||||
$$childHead: IScope;
|
||||
$$nextSibling: IScope;
|
||||
[key: string]: any;
|
||||
}
|
||||
export interface IScope extends IRootScopeService {}
|
||||
|
||||
export interface IAngularBootstrapConfig { strictDi?: boolean; }
|
||||
export interface IDirective {
|
||||
compile?: IDirectiveCompileFn;
|
||||
controller?: IController;
|
||||
controllerAs?: string;
|
||||
bindToController?: boolean|{[key: string]: string};
|
||||
link?: IDirectiveLinkFn|IDirectivePrePost;
|
||||
name?: string;
|
||||
priority?: number;
|
||||
replace?: boolean;
|
||||
require?: DirectiveRequireProperty;
|
||||
restrict?: string;
|
||||
scope?: boolean|{[key: string]: string};
|
||||
template?: string|Function;
|
||||
templateUrl?: string|Function;
|
||||
templateNamespace?: string;
|
||||
terminal?: boolean;
|
||||
transclude?: boolean|'element'|{[key: string]: string};
|
||||
}
|
||||
export type DirectiveRequireProperty = SingleOrListOrMap<string>;
|
||||
export interface IDirectiveCompileFn {
|
||||
(templateElement: IAugmentedJQuery, templateAttributes: IAttributes,
|
||||
transclude: ITranscludeFunction): IDirectivePrePost;
|
||||
}
|
||||
export interface IDirectivePrePost {
|
||||
pre?: IDirectiveLinkFn;
|
||||
post?: IDirectiveLinkFn;
|
||||
}
|
||||
export interface IDirectiveLinkFn {
|
||||
(scope: IScope, instanceElement: IAugmentedJQuery, instanceAttributes: IAttributes,
|
||||
controller: any, transclude: ITranscludeFunction): void;
|
||||
}
|
||||
export interface IComponent {
|
||||
bindings?: {[key: string]: string};
|
||||
controller?: string|IInjectable;
|
||||
controllerAs?: string;
|
||||
require?: DirectiveRequireProperty;
|
||||
template?: string|Function;
|
||||
templateUrl?: string|Function;
|
||||
transclude?: boolean;
|
||||
}
|
||||
export interface IAttributes { $observe(attr: string, fn: (v: string) => void): void; }
|
||||
export interface ITranscludeFunction {
|
||||
// If the scope is provided, then the cloneAttachFn must be as well.
|
||||
(scope: IScope, cloneAttachFn: ICloneAttachFunction): IAugmentedJQuery;
|
||||
// If one argument is provided, then it's assumed to be the cloneAttachFn.
|
||||
(cloneAttachFn?: ICloneAttachFunction): IAugmentedJQuery;
|
||||
}
|
||||
export interface ICloneAttachFunction {
|
||||
// Let's hint but not force cloneAttachFn's signature
|
||||
(clonedElement?: IAugmentedJQuery, scope?: IScope): any;
|
||||
}
|
||||
export type IAugmentedJQuery = Node[] & {
|
||||
bind?: (name: string, fn: () => void) => void;
|
||||
data?: (name: string, value?: any) => any;
|
||||
text?: () => string;
|
||||
inheritedData?: (name: string, value?: any) => any;
|
||||
contents?: () => IAugmentedJQuery;
|
||||
parent?: () => IAugmentedJQuery;
|
||||
empty?: () => void;
|
||||
append?: (content: IAugmentedJQuery | string) => IAugmentedJQuery;
|
||||
controller?: (name: string) => any;
|
||||
isolateScope?: () => IScope;
|
||||
injector?: () => IInjectorService;
|
||||
};
|
||||
export interface IProvider { $get: IInjectable; }
|
||||
export interface IProvideService {
|
||||
provider(token: Ng1Token, provider: IProvider): IProvider;
|
||||
factory(token: Ng1Token, factory: IInjectable): IProvider;
|
||||
service(token: Ng1Token, type: IInjectable): IProvider;
|
||||
value(token: Ng1Token, value: any): IProvider;
|
||||
constant(token: Ng1Token, value: any): void;
|
||||
decorator(token: Ng1Token, factory: IInjectable): void;
|
||||
}
|
||||
export interface IParseService { (expression: string): ICompiledExpression; }
|
||||
export interface ICompiledExpression { assign(context: any, value: any): any; }
|
||||
export interface IHttpBackendService {
|
||||
(method: string, url: string, post?: any, callback?: Function, headers?: any, timeout?: number,
|
||||
withCredentials?: boolean): void;
|
||||
}
|
||||
export interface ICacheObject {
|
||||
put<T>(key: string, value?: T): T;
|
||||
get(key: string): any;
|
||||
}
|
||||
export interface ITemplateCacheService extends ICacheObject {}
|
||||
export interface ITemplateRequestService {
|
||||
(template: string|any /* TrustedResourceUrl */, ignoreRequestError?: boolean): Promise<string>;
|
||||
totalPendingRequests: number;
|
||||
}
|
||||
export type IController = string | IInjectable;
|
||||
export interface IControllerService {
|
||||
(controllerConstructor: IController, locals?: any, later?: any, ident?: any): any;
|
||||
(controllerName: string, locals?: any): any;
|
||||
}
|
||||
|
||||
export interface IInjectorService {
|
||||
get(key: string): any;
|
||||
has(key: string): boolean;
|
||||
}
|
||||
|
||||
export interface ITestabilityService {
|
||||
findBindings(element: Element, expression: string, opt_exactMatch?: boolean): Element[];
|
||||
findModels(element: Element, expression: string, opt_exactMatch?: boolean): Element[];
|
||||
getLocation(): string;
|
||||
setLocation(url: string): void;
|
||||
whenStable(callback: Function): void;
|
||||
}
|
||||
|
||||
export interface INgModelController {
|
||||
$render(): void;
|
||||
$isEmpty(value: any): boolean;
|
||||
$setValidity(validationErrorKey: string, isValid: boolean): void;
|
||||
$setPristine(): void;
|
||||
$setDirty(): void;
|
||||
$setUntouched(): void;
|
||||
$setTouched(): void;
|
||||
$rollbackViewValue(): void;
|
||||
$validate(): void;
|
||||
$commitViewValue(): void;
|
||||
$setViewValue(value: any, trigger: string): void;
|
||||
|
||||
$viewValue: any;
|
||||
$modelValue: any;
|
||||
$parsers: Function[];
|
||||
$formatters: Function[];
|
||||
$validators: {[key: string]: Function};
|
||||
$asyncValidators: {[key: string]: Function};
|
||||
$viewChangeListeners: Function[];
|
||||
$error: Object;
|
||||
$pending: Object;
|
||||
$untouched: boolean;
|
||||
$touched: boolean;
|
||||
$pristine: boolean;
|
||||
$dirty: boolean;
|
||||
$valid: boolean;
|
||||
$invalid: boolean;
|
||||
$name: string;
|
||||
}
|
||||
|
||||
function noNg() {
|
||||
throw new Error('AngularJS v1.x is not loaded!');
|
||||
}
|
||||
|
||||
let angular: {
|
||||
bootstrap: (e: Element, modules: (string | IInjectable)[], config: IAngularBootstrapConfig) =>
|
||||
void,
|
||||
module: (prefix: string, dependencies?: string[]) => IModule,
|
||||
element: (e: Element) => IAugmentedJQuery,
|
||||
version: {major: number}, resumeBootstrap?: () => void,
|
||||
getTestability: (e: Element) => ITestabilityService
|
||||
} = <any>{
|
||||
bootstrap: noNg,
|
||||
module: noNg,
|
||||
element: noNg,
|
||||
version: noNg,
|
||||
resumeBootstrap: noNg,
|
||||
getTestability: noNg
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
if (window.hasOwnProperty('angular')) {
|
||||
angular = (<any>window).angular;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore in CJS mode.
|
||||
}
|
||||
|
||||
export const bootstrap = angular.bootstrap;
|
||||
export const module = angular.module;
|
||||
export const element = angular.element;
|
||||
export const version = angular.version;
|
||||
export const resumeBootstrap = angular.resumeBootstrap;
|
||||
export const getTestability = angular.getTestability;
|
47
packages/upgrade/src/common/component_info.ts
Normal file
47
packages/upgrade/src/common/component_info.ts
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
export interface ComponentInfo {
|
||||
component: Type<any>;
|
||||
inputs?: string[];
|
||||
outputs?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A `PropertyBinding` represents a mapping between a property name
|
||||
* and an attribute name. It is parsed from a string of the form
|
||||
* `"prop: attr"`; or simply `"propAndAttr" where the property
|
||||
* and attribute have the same identifier.
|
||||
*/
|
||||
export class PropertyBinding {
|
||||
prop: string;
|
||||
attr: string;
|
||||
bracketAttr: string;
|
||||
bracketParenAttr: string;
|
||||
parenAttr: string;
|
||||
onAttr: string;
|
||||
bindAttr: string;
|
||||
bindonAttr: string;
|
||||
|
||||
constructor(public binding: string) { this.parseBinding(); }
|
||||
|
||||
private parseBinding() {
|
||||
const parts = this.binding.split(':');
|
||||
this.prop = parts[0].trim();
|
||||
this.attr = (parts[1] || this.prop).trim();
|
||||
this.bracketAttr = `[${this.attr}]`;
|
||||
this.parenAttr = `(${this.attr})`;
|
||||
this.bracketParenAttr = `[(${this.attr})]`;
|
||||
const capitalAttr = this.attr.charAt(0).toUpperCase() + this.attr.substr(1);
|
||||
this.onAttr = `on${capitalAttr}`;
|
||||
this.bindAttr = `bind${capitalAttr}`;
|
||||
this.bindonAttr = `bindon${capitalAttr}`;
|
||||
}
|
||||
}
|
31
packages/upgrade/src/common/constants.ts
Normal file
31
packages/upgrade/src/common/constants.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export const $COMPILE = '$compile';
|
||||
export const $CONTROLLER = '$controller';
|
||||
export const $DELEGATE = '$delegate';
|
||||
export const $HTTP_BACKEND = '$httpBackend';
|
||||
export const $INJECTOR = '$injector';
|
||||
export const $PARSE = '$parse';
|
||||
export const $PROVIDE = '$provide';
|
||||
export const $ROOT_SCOPE = '$rootScope';
|
||||
export const $SCOPE = '$scope';
|
||||
export const $TEMPLATE_CACHE = '$templateCache';
|
||||
export const $TEMPLATE_REQUEST = '$templateRequest';
|
||||
|
||||
export const $$TESTABILITY = '$$testability';
|
||||
|
||||
export const COMPILER_KEY = '$$angularCompiler';
|
||||
export const GROUP_PROJECTABLE_NODES_KEY = '$$angularGroupProjectableNodes';
|
||||
export const INJECTOR_KEY = '$$angularInjector';
|
||||
export const NG_ZONE_KEY = '$$angularNgZone';
|
||||
|
||||
export const REQUIRE_INJECTOR = '?^^' + INJECTOR_KEY;
|
||||
export const REQUIRE_NG_MODEL = '?ngModel';
|
||||
|
||||
export const UPGRADE_MODULE_NAME = '$$UpgradeModule';
|
20
packages/upgrade/src/common/content_projection_helper.ts
Normal file
20
packages/upgrade/src/common/content_projection_helper.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
import * as angular from './angular1';
|
||||
|
||||
|
||||
export class ContentProjectionHelper {
|
||||
groupProjectableNodes($injector: angular.IInjectorService, component: Type<any>, nodes: Node[]):
|
||||
Node[][] {
|
||||
// By default, do not support multi-slot projection,
|
||||
// as `upgrade/static` does not support it yet.
|
||||
return [nodes];
|
||||
}
|
||||
}
|
167
packages/upgrade/src/common/downgrade_component.ts
Normal file
167
packages/upgrade/src/common/downgrade_component.ts
Normal file
@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ComponentFactory, ComponentFactoryResolver, Injector, Type} from '@angular/core';
|
||||
|
||||
import * as angular from './angular1';
|
||||
import {$COMPILE, $INJECTOR, $PARSE, INJECTOR_KEY, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from './constants';
|
||||
import {DowngradeComponentAdapter} from './downgrade_component_adapter';
|
||||
import {controllerKey, getComponentName} from './util';
|
||||
|
||||
let downgradeCount = 0;
|
||||
|
||||
/**
|
||||
* @whatItDoes
|
||||
*
|
||||
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
|
||||
* library for hybrid upgrade apps that support AoT compilation*
|
||||
*
|
||||
* Allows an Angular component to be used from AngularJS.
|
||||
*
|
||||
* @howToUse
|
||||
*
|
||||
* Let's assume that you have an Angular component called `ng2Heroes` that needs
|
||||
* to be made available in AngularJS templates.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng2-heroes"}
|
||||
*
|
||||
* We must create an AngularJS [directive](https://docs.angularjs.org/guide/directive)
|
||||
* that will make this Angular component available inside AngularJS templates.
|
||||
* The `downgradeComponent()` function returns a factory function that we
|
||||
* can use to define the AngularJS directive that wraps the "downgraded" component.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng2-heroes-wrapper"}
|
||||
*
|
||||
* In this example you can see that we must provide information about the component being
|
||||
* "downgraded". This is because once the AoT compiler has run, all metadata about the
|
||||
* component has been removed from the code, and so cannot be inferred.
|
||||
*
|
||||
* We must do the following:
|
||||
* * specify the Angular component class that is to be downgraded
|
||||
* * specify all inputs and outputs that the AngularJS component expects
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* A helper function that returns a factory function to be used for registering an
|
||||
* AngularJS wrapper directive for "downgrading" an Angular component.
|
||||
*
|
||||
* The parameter contains information about the Component that is being downgraded:
|
||||
*
|
||||
* * `component: Type<any>`: The type of the Component that will be downgraded
|
||||
* * `inputs: string[]`: A collection of strings that specify what inputs the component accepts.
|
||||
* * `outputs: string[]`: A collection of strings that specify what outputs the component emits.
|
||||
*
|
||||
* The `inputs` and `outputs` are strings that map the names of properties to camelCased
|
||||
* attribute names. They are of the form `"prop: attr"`; or simply `"propAndAttr" where the
|
||||
* property and attribute have the same identifier.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function downgradeComponent(info: /* ComponentInfo */ {
|
||||
component: Type<any>;
|
||||
inputs?: string[];
|
||||
outputs?: string[];
|
||||
}): any /* angular.IInjectable */ {
|
||||
const idPrefix = `NG2_UPGRADE_${downgradeCount++}_`;
|
||||
let idCount = 0;
|
||||
|
||||
const directiveFactory:
|
||||
angular.IAnnotatedFunction = function(
|
||||
$compile: angular.ICompileService,
|
||||
$injector: angular.IInjectorService,
|
||||
$parse: angular.IParseService): angular.IDirective {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
terminal: true,
|
||||
require: [REQUIRE_INJECTOR, REQUIRE_NG_MODEL],
|
||||
link: (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes,
|
||||
required: any[]) => {
|
||||
// We might have to compile the contents asynchronously, because this might have been
|
||||
// triggered by `UpgradeNg1ComponentAdapterBuilder`, before the Angular templates have
|
||||
// been compiled.
|
||||
|
||||
const parentInjector: Injector|ParentInjectorPromise =
|
||||
required[0] || $injector.get(INJECTOR_KEY);
|
||||
const ngModel: angular.INgModelController = required[1];
|
||||
|
||||
const downgradeFn = (injector: Injector) => {
|
||||
const componentFactoryResolver: ComponentFactoryResolver =
|
||||
injector.get(ComponentFactoryResolver);
|
||||
const componentFactory: ComponentFactory<any> =
|
||||
componentFactoryResolver.resolveComponentFactory(info.component);
|
||||
|
||||
if (!componentFactory) {
|
||||
throw new Error('Expecting ComponentFactory for: ' + getComponentName(info.component));
|
||||
}
|
||||
|
||||
const id = idPrefix + (idCount++);
|
||||
const injectorPromise = new ParentInjectorPromise(element);
|
||||
const facade = new DowngradeComponentAdapter(
|
||||
id, info, element, attrs, scope, ngModel, injector, $injector, $compile, $parse,
|
||||
componentFactory);
|
||||
|
||||
const projectableNodes = facade.compileContents();
|
||||
facade.createComponent(projectableNodes);
|
||||
facade.setupInputs();
|
||||
facade.setupOutputs();
|
||||
facade.registerCleanup();
|
||||
|
||||
injectorPromise.resolve(facade.getInjector());
|
||||
};
|
||||
|
||||
if (parentInjector instanceof ParentInjectorPromise) {
|
||||
parentInjector.then(downgradeFn);
|
||||
} else {
|
||||
downgradeFn(parentInjector);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// bracket-notation because of closure - see #14441
|
||||
directiveFactory['$inject'] = [$COMPILE, $INJECTOR, $PARSE];
|
||||
return directiveFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronous promise-like object to wrap parent injectors,
|
||||
* to preserve the synchronous nature of Angular 1's $compile.
|
||||
*/
|
||||
class ParentInjectorPromise {
|
||||
private injector: Injector;
|
||||
private injectorKey: string = controllerKey(INJECTOR_KEY);
|
||||
private callbacks: ((injector: Injector) => any)[] = [];
|
||||
|
||||
constructor(private element: angular.IAugmentedJQuery) {
|
||||
// Store the promise on the element.
|
||||
element.data(this.injectorKey, this);
|
||||
}
|
||||
|
||||
then(callback: (injector: Injector) => any) {
|
||||
if (this.injector) {
|
||||
callback(this.injector);
|
||||
} else {
|
||||
this.callbacks.push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(injector: Injector) {
|
||||
this.injector = injector;
|
||||
|
||||
// Store the real injector on the element.
|
||||
this.element.data(this.injectorKey, injector);
|
||||
|
||||
// Release the element to prevent memory leaks.
|
||||
this.element = null;
|
||||
|
||||
// Run the queued callbacks.
|
||||
this.callbacks.forEach(callback => callback(injector));
|
||||
this.callbacks.length = 0;
|
||||
}
|
||||
}
|
189
packages/upgrade/src/common/downgrade_component_adapter.ts
Normal file
189
packages/upgrade/src/common/downgrade_component_adapter.ts
Normal file
@ -0,0 +1,189 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, ReflectiveInjector, SimpleChange, SimpleChanges, Type} from '@angular/core';
|
||||
|
||||
import * as angular from './angular1';
|
||||
import {ComponentInfo, PropertyBinding} from './component_info';
|
||||
import {$SCOPE} from './constants';
|
||||
import {ContentProjectionHelper} from './content_projection_helper';
|
||||
import {getComponentName, hookupNgModel} from './util';
|
||||
|
||||
const INITIAL_VALUE = {
|
||||
__UNINITIALIZED__: true
|
||||
};
|
||||
|
||||
export class DowngradeComponentAdapter {
|
||||
private inputChangeCount: number = 0;
|
||||
private inputChanges: SimpleChanges = null;
|
||||
private componentScope: angular.IScope;
|
||||
private componentRef: ComponentRef<any> = null;
|
||||
private component: any = null;
|
||||
private changeDetector: ChangeDetectorRef = null;
|
||||
|
||||
constructor(
|
||||
private id: string, private info: ComponentInfo, private element: angular.IAugmentedJQuery,
|
||||
private attrs: angular.IAttributes, private scope: angular.IScope,
|
||||
private ngModel: angular.INgModelController, private parentInjector: Injector,
|
||||
private $injector: angular.IInjectorService, private $compile: angular.ICompileService,
|
||||
private $parse: angular.IParseService, private componentFactory: ComponentFactory<any>) {
|
||||
(this.element[0] as any).id = id;
|
||||
this.componentScope = scope.$new();
|
||||
}
|
||||
|
||||
compileContents(): Node[][] {
|
||||
const compiledProjectableNodes: Node[][] = [];
|
||||
|
||||
// The projected content has to be grouped, before it is compiled.
|
||||
const projectionHelper: ContentProjectionHelper =
|
||||
this.parentInjector.get(ContentProjectionHelper);
|
||||
const projectableNodes: Node[][] = projectionHelper.groupProjectableNodes(
|
||||
this.$injector, this.info.component, this.element.contents());
|
||||
const linkFns = projectableNodes.map(nodes => this.$compile(nodes));
|
||||
|
||||
this.element.empty();
|
||||
|
||||
linkFns.forEach(linkFn => {
|
||||
linkFn(this.scope, (clone: Node[]) => {
|
||||
compiledProjectableNodes.push(clone);
|
||||
this.element.append(clone);
|
||||
});
|
||||
});
|
||||
|
||||
return compiledProjectableNodes;
|
||||
}
|
||||
|
||||
createComponent(projectableNodes: Node[][]) {
|
||||
const childInjector = ReflectiveInjector.resolveAndCreate(
|
||||
[{provide: $SCOPE, useValue: this.componentScope}], this.parentInjector);
|
||||
|
||||
this.componentRef =
|
||||
this.componentFactory.create(childInjector, projectableNodes, this.element[0]);
|
||||
this.changeDetector = this.componentRef.changeDetectorRef;
|
||||
this.component = this.componentRef.instance;
|
||||
|
||||
hookupNgModel(this.ngModel, this.component);
|
||||
}
|
||||
|
||||
setupInputs(): void {
|
||||
const attrs = this.attrs;
|
||||
const inputs = this.info.inputs || [];
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
const input = new PropertyBinding(inputs[i]);
|
||||
let expr: any /** TODO #9100 */ = null;
|
||||
|
||||
if (attrs.hasOwnProperty(input.attr)) {
|
||||
const observeFn = (prop => {
|
||||
let prevValue = INITIAL_VALUE;
|
||||
return (currValue: any) => {
|
||||
if (prevValue === INITIAL_VALUE) {
|
||||
prevValue = currValue;
|
||||
}
|
||||
|
||||
this.updateInput(prop, prevValue, currValue);
|
||||
prevValue = currValue;
|
||||
};
|
||||
})(input.prop);
|
||||
attrs.$observe(input.attr, observeFn);
|
||||
|
||||
} else if (attrs.hasOwnProperty(input.bindAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[input.bindAttr];
|
||||
} else if (attrs.hasOwnProperty(input.bracketAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[input.bracketAttr];
|
||||
} else if (attrs.hasOwnProperty(input.bindonAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[input.bindonAttr];
|
||||
} else if (attrs.hasOwnProperty(input.bracketParenAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[input.bracketParenAttr];
|
||||
}
|
||||
if (expr != null) {
|
||||
const watchFn =
|
||||
(prop => (currValue: any, prevValue: any) =>
|
||||
this.updateInput(prop, prevValue, currValue))(input.prop);
|
||||
this.componentScope.$watch(expr, watchFn);
|
||||
}
|
||||
}
|
||||
|
||||
const prototype = this.info.component.prototype;
|
||||
if (prototype && (<OnChanges>prototype).ngOnChanges) {
|
||||
// Detect: OnChanges interface
|
||||
this.inputChanges = {};
|
||||
this.componentScope.$watch(() => this.inputChangeCount, () => {
|
||||
const inputChanges = this.inputChanges;
|
||||
this.inputChanges = {};
|
||||
(<OnChanges>this.component).ngOnChanges(inputChanges);
|
||||
});
|
||||
}
|
||||
this.componentScope.$watch(() => this.changeDetector && this.changeDetector.detectChanges());
|
||||
}
|
||||
|
||||
setupOutputs() {
|
||||
const attrs = this.attrs;
|
||||
const outputs = this.info.outputs || [];
|
||||
for (let j = 0; j < outputs.length; j++) {
|
||||
const output = new PropertyBinding(outputs[j]);
|
||||
let expr: any /** TODO #9100 */ = null;
|
||||
let assignExpr = false;
|
||||
|
||||
const bindonAttr =
|
||||
output.bindonAttr ? output.bindonAttr.substring(0, output.bindonAttr.length - 6) : null;
|
||||
const bracketParenAttr = output.bracketParenAttr ?
|
||||
`[(${output.bracketParenAttr.substring(2, output.bracketParenAttr.length - 8)})]` :
|
||||
null;
|
||||
|
||||
if (attrs.hasOwnProperty(output.onAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[output.onAttr];
|
||||
} else if (attrs.hasOwnProperty(output.parenAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[output.parenAttr];
|
||||
} else if (attrs.hasOwnProperty(bindonAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[bindonAttr];
|
||||
assignExpr = true;
|
||||
} else if (attrs.hasOwnProperty(bracketParenAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[bracketParenAttr];
|
||||
assignExpr = true;
|
||||
}
|
||||
|
||||
if (expr != null && assignExpr != null) {
|
||||
const getter = this.$parse(expr);
|
||||
const setter = getter.assign;
|
||||
if (assignExpr && !setter) {
|
||||
throw new Error(`Expression '${expr}' is not assignable!`);
|
||||
}
|
||||
const emitter = this.component[output.prop] as EventEmitter<any>;
|
||||
if (emitter) {
|
||||
emitter.subscribe({
|
||||
next: assignExpr ?
|
||||
((setter: any) => (v: any /** TODO #9100 */) => setter(this.scope, v))(setter) :
|
||||
((getter: any) => (v: any /** TODO #9100 */) =>
|
||||
getter(this.scope, {$event: v}))(getter)
|
||||
});
|
||||
} else {
|
||||
throw new Error(
|
||||
`Missing emitter '${output.prop}' on component '${getComponentName(this.info.component)}'!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerCleanup() {
|
||||
this.element.bind('$destroy', () => {
|
||||
this.componentScope.$destroy();
|
||||
this.componentRef.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
getInjector(): Injector { return this.componentRef && this.componentRef.injector; }
|
||||
|
||||
private updateInput(prop: string, prevValue: any, currValue: any) {
|
||||
if (this.inputChanges) {
|
||||
this.inputChangeCount++;
|
||||
this.inputChanges[prop] = new SimpleChange(prevValue, currValue, prevValue === currValue);
|
||||
}
|
||||
|
||||
this.component[prop] = currValue;
|
||||
}
|
||||
}
|
59
packages/upgrade/src/common/downgrade_injectable.ts
Normal file
59
packages/upgrade/src/common/downgrade_injectable.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injector} from '@angular/core';
|
||||
import {INJECTOR_KEY} from './constants';
|
||||
|
||||
/**
|
||||
* @whatItDoes
|
||||
*
|
||||
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
|
||||
* library for hybrid upgrade apps that support AoT compilation*
|
||||
*
|
||||
* Allow an Angular service to be accessible from AngularJS.
|
||||
*
|
||||
* @howToUse
|
||||
*
|
||||
* First ensure that the service to be downgraded is provided in an {@link NgModule}
|
||||
* that will be part of the upgrade application. For example, let's assume we have
|
||||
* defined `HeroesService`
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng2-heroes-service"}
|
||||
*
|
||||
* and that we have included this in our upgrade app {@link NgModule}
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="ng2-module"}
|
||||
*
|
||||
* Now we can register the `downgradeInjectable` factory function for the service
|
||||
* on an AngularJS module.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="downgrade-ng2-heroes-service"}
|
||||
*
|
||||
* Inside an AngularJS component's controller we can get hold of the
|
||||
* downgraded service via the name we gave when downgrading.
|
||||
*
|
||||
* {@example upgrade/static/ts/module.ts region="example-app"}
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* Takes a `token` that identifies a service provided from Angular.
|
||||
*
|
||||
* Returns a [factory function](https://docs.angularjs.org/guide/di) that can be
|
||||
* used to register the service on an AngularJS module.
|
||||
*
|
||||
* The factory function provides access to the Angular service that
|
||||
* is identified by the `token` parameter.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function downgradeInjectable(token: any): Function {
|
||||
const factory = function(i: Injector) { return i.get(token); };
|
||||
(factory as any).$inject = [INJECTOR_KEY];
|
||||
|
||||
return factory;
|
||||
}
|
77
packages/upgrade/src/common/util.ts
Normal file
77
packages/upgrade/src/common/util.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
import * as angular from './angular1';
|
||||
|
||||
export function onError(e: any) {
|
||||
// TODO: (misko): We seem to not have a stack trace here!
|
||||
if (console.error) {
|
||||
console.error(e, e.stack);
|
||||
} else {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(e, e.stack);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
export function controllerKey(name: string): string {
|
||||
return '$' + name + 'Controller';
|
||||
}
|
||||
|
||||
export function getAttributesAsArray(node: Node): [string, string][] {
|
||||
const attributes = node.attributes;
|
||||
let asArray: [string, string][];
|
||||
if (attributes) {
|
||||
let attrLen = attributes.length;
|
||||
asArray = new Array(attrLen);
|
||||
for (let i = 0; i < attrLen; i++) {
|
||||
asArray[i] = [attributes[i].nodeName, attributes[i].nodeValue];
|
||||
}
|
||||
}
|
||||
return asArray || [];
|
||||
}
|
||||
|
||||
export function getComponentName(component: Type<any>): string {
|
||||
// Return the name of the component or the first line of its stringified version.
|
||||
return (component as any).overriddenName || component.name || component.toString().split('\n')[0];
|
||||
}
|
||||
|
||||
export class Deferred<R> {
|
||||
promise: Promise<R>;
|
||||
resolve: (value?: R|PromiseLike<R>) => void;
|
||||
reject: (error?: any) => void;
|
||||
|
||||
constructor() {
|
||||
this.promise = new Promise((res, rej) => {
|
||||
this.resolve = res;
|
||||
this.reject = rej;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the passed-in component implements the subset of the
|
||||
* `ControlValueAccessor` interface needed for AngularJS `ng-model`
|
||||
* compatibility.
|
||||
*/
|
||||
function supportsNgModel(component: any) {
|
||||
return typeof component.writeValue === 'function' &&
|
||||
typeof component.registerOnChange === 'function';
|
||||
}
|
||||
|
||||
/**
|
||||
* Glue the AngularJS `NgModelController` (if it exists) to the component
|
||||
* (if it implements the needed subset of the `ControlValueAccessor` interface).
|
||||
*/
|
||||
export function hookupNgModel(ngModel: angular.INgModelController, component: any) {
|
||||
if (ngModel && supportsNgModel(component)) {
|
||||
ngModel.$render = () => { component.writeValue(ngModel.$viewValue); };
|
||||
component.registerOnChange(ngModel.$setViewValue.bind(ngModel));
|
||||
}
|
||||
}
|
19
packages/upgrade/src/common/version.ts
Normal file
19
packages/upgrade/src/common/version.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
* @description
|
||||
* Entry point for all public APIs of the common package.
|
||||
*/
|
||||
|
||||
import {Version} from '@angular/core';
|
||||
/**
|
||||
* @stable
|
||||
*/
|
||||
export const VERSION = new Version('0.0.0-PLACEHOLDER');
|
Reference in New Issue
Block a user