Miško Hevery 60065935be refactor(ivy): align compiler with runtime ()
Remove `containerRefreshStart` and `containerRefreshEnd` instruction
from the output.

Generate directives as a list in `componentDef` rather than inline into
instructions. This is consistent in making selector resolution runtime
so that translation of templates can follow locality.

PR Close 
2018-04-02 15:49:48 -07:00

439 lines
13 KiB
TypeScript

/**
* @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 {SimpleChange} from '../change_detection/change_detection_util';
import {ChangeDetectionStrategy} from '../change_detection/constants';
import {PipeTransform} from '../change_detection/pipe_transform';
import {Provider} from '../core';
import {OnChanges, SimpleChanges} from '../metadata/lifecycle_hooks';
import {RendererType2} from '../render/api';
import {Type} from '../type';
import {resolveRendererType2} from '../view/util';
import {diPublic} from './di';
import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFeature, DirectiveDefListOrFactory, DirectiveType, DirectiveTypesOrFactory, PipeDef, PipeType, PipeTypesOrFactory} from './interfaces/definition';
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
/**
* Create a component definition object.
*
*
* # Example
* ```
* class MyDirective {
* // Generated by Angular Template Compiler
* // [Symbol] syntax will not be supported by TypeScript until v2.7
* static ngComponentDef = defineComponent({
* ...
* });
* }
* ```
*/
export function defineComponent<T>(componentDefinition: {
/**
* Directive type, needed to configure the injector.
*/
type: Type<T>;
/** The selectors that will be used to match nodes to this component. */
selectors: CssSelectorList;
/**
* Factory method used to create an instance of directive.
*/
factory: () => T | ({0: T} & any[]); /* trying to say T | [T, ...any] */
/**
* Static attributes to set on host element.
*
* Even indices: attribute name
* Odd indices: attribute value
*/
attributes?: string[];
/**
* A map of input names.
*
* The format is in: `{[actualPropertyName: string]:string}`.
*
* Which the minifier may translate to: `{[minifiedPropertyName: string]:string}`.
*
* This allows the render to re-construct the minified and non-minified names
* of properties.
*/
inputs?: {[P in keyof T]?: string};
/**
* A map of output names.
*
* The format is in: `{[actualPropertyName: string]:string}`.
*
* Which the minifier may translate to: `{[minifiedPropertyName: string]:string}`.
*
* This allows the render to re-construct the minified and non-minified names
* of properties.
*/
outputs?: {[P in keyof T]?: string};
/**
* Function executed by the parent template to allow child directive to apply host bindings.
*/
hostBindings?: (directiveIndex: number, elementIndex: number) => void;
/**
* Defines the name that can be used in the template to assign this directive to a variable.
*
* See: {@link Directive.exportAs}
*/
exportAs?: string;
/**
* Template function use for rendering DOM.
*
* This function has following structure.
*
* ```
* function Template<T>(ctx:T, creationMode: boolean) {
* if (creationMode) {
* // Contains creation mode instructions.
* }
* // Contains binding update instructions
* }
* ```
*
* Common instructions are:
* Creation mode instructions:
* - `elementStart`, `elementEnd`
* - `text`
* - `container`
* - `listener`
*
* Binding update instructions:
* - `bind`
* - `elementAttribute`
* - `elementProperty`
* - `elementClass`
* - `elementStyle`
*
*/
template: ComponentTemplate<T>;
/**
* A list of optional features to apply.
*
* See: {@link NgOnChangesFeature}, {@link PublicFeature}
*/
features?: ComponentDefFeature[];
rendererType?: RendererType2;
changeDetection?: ChangeDetectionStrategy;
/**
* Defines the set of injectable objects that are visible to a Directive and its light DOM
* children.
*/
providers?: Provider[];
/**
* Defines the set of injectable objects that are visible to its view DOM children.
*/
viewProviders?: Provider[];
/**
* Registry of directives and components that may be found in this component's view.
*
* The property is either an array of `DirectiveDef`s or a function which returns the array of
* `DirectiveDef`s. The function is necessary to be able to support forward declarations.
*/
directives?: DirectiveTypesOrFactory | null;
/**
* Registry of pipes that may be found in this component's view.
*
* The property is either an array of `PipeDefs`s or a function which returns the array of
* `PipeDefs`s. The function is necessary to be able to support forward declarations.
*/
pipes?: PipeTypesOrFactory | null;
}): ComponentDef<T> {
const type = componentDefinition.type;
const pipeTypes = componentDefinition.pipes !;
const directiveTypes = componentDefinition.directives !;
const def = <ComponentDef<any>>{
type: type,
diPublic: null,
factory: componentDefinition.factory,
template: componentDefinition.template || null !,
hostBindings: componentDefinition.hostBindings || null,
attributes: componentDefinition.attributes || null,
inputs: invertObject(componentDefinition.inputs),
outputs: invertObject(componentDefinition.outputs),
rendererType: resolveRendererType2(componentDefinition.rendererType) || null,
exportAs: componentDefinition.exportAs,
onInit: type.prototype.ngOnInit || null,
doCheck: type.prototype.ngDoCheck || null,
afterContentInit: type.prototype.ngAfterContentInit || null,
afterContentChecked: type.prototype.ngAfterContentChecked || null,
afterViewInit: type.prototype.ngAfterViewInit || null,
afterViewChecked: type.prototype.ngAfterViewChecked || null,
onDestroy: type.prototype.ngOnDestroy || null,
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
directiveDefs: directiveTypes ?
() => (typeof directiveTypes === 'function' ? directiveTypes() : directiveTypes)
.map(extractDirectiveDef) :
null,
pipeDefs: pipeTypes ?
() => (typeof pipeTypes === 'function' ? pipeTypes() : pipeTypes).map(extractPipeDef) :
null,
selectors: componentDefinition.selectors
};
const feature = componentDefinition.features;
feature && feature.forEach((fn) => fn(def));
return def;
}
export function extractDirectiveDef(type: DirectiveType<any>& ComponentType<any>):
DirectiveDef<any>|ComponentDef<any> {
const def = type.ngComponentDef || type.ngDirectiveDef;
if (ngDevMode && !def) {
throw new Error(`'${type.name}' is neither 'ComponentType' or 'DirectiveType'.`);
}
return def;
}
export function extractPipeDef(type: PipeType<any>): PipeDef<any> {
const def = type.ngPipeDef;
if (ngDevMode && !def) {
throw new Error(`'${type.name}' is not a 'PipeType'.`);
}
return def;
}
const PRIVATE_PREFIX = '__ngOnChanges_';
type OnChangesExpando = OnChanges & {
__ngOnChanges_: SimpleChanges|null|undefined;
[key: string]: any;
};
/**
* Creates an NgOnChangesFeature function for a component's features list.
*
* It accepts an optional map of minified input property names to original property names,
* if any input properties have a public alias.
*
* The NgOnChangesFeature function that is returned decorates a component with support for
* the ngOnChanges lifecycle hook, so it should be included in any component that implements
* that hook.
*
* Example usage:
*
* ```
* static ngComponentDef = defineComponent({
* ...
* inputs: {name: 'publicName'},
* features: [NgOnChangesFeature({name: 'name'})]
* });
* ```
*
* @param inputPropertyNames Map of input property names, if they are aliased
* @returns DirectiveDefFeature
*/
export function NgOnChangesFeature(inputPropertyNames?: {[key: string]: string}):
DirectiveDefFeature {
return function(definition: DirectiveDef<any>): void {
const inputs = definition.inputs;
const proto = definition.type.prototype;
// Place where we will store SimpleChanges if there is a change
Object.defineProperty(proto, PRIVATE_PREFIX, {value: undefined, writable: true});
for (let pubKey in inputs) {
const minKey = inputs[pubKey];
const propertyName = inputPropertyNames && inputPropertyNames[minKey] || pubKey;
const privateMinKey = PRIVATE_PREFIX + minKey;
// Create a place where the actual value will be stored and make it non-enumerable
Object.defineProperty(proto, privateMinKey, {value: undefined, writable: true});
const existingDesc = Object.getOwnPropertyDescriptor(proto, minKey);
// create a getter and setter for property
Object.defineProperty(proto, minKey, {
get: function(this: OnChangesExpando) {
return (existingDesc && existingDesc.get) ? existingDesc.get.call(this) :
this[privateMinKey];
},
set: function(this: OnChangesExpando, value: any) {
let simpleChanges = this[PRIVATE_PREFIX];
let isFirstChange = simpleChanges === undefined;
if (simpleChanges == null) {
simpleChanges = this[PRIVATE_PREFIX] = {};
}
simpleChanges[propertyName] = new SimpleChange(this[privateMinKey], value, isFirstChange);
(existingDesc && existingDesc.set) ? existingDesc.set.call(this, value) :
this[privateMinKey] = value;
}
});
}
// If an onInit hook is defined, it will need to wrap the ngOnChanges call
// so the call order is changes-init-check in creation mode. In subsequent
// change detection runs, only the check wrapper will be called.
if (definition.onInit != null) {
definition.onInit = onChangesWrapper(definition.onInit);
}
definition.doCheck = onChangesWrapper(definition.doCheck);
};
function onChangesWrapper(delegateHook: (() => void) | null) {
return function(this: OnChangesExpando) {
let simpleChanges = this[PRIVATE_PREFIX];
if (simpleChanges != null) {
this.ngOnChanges(simpleChanges);
this[PRIVATE_PREFIX] = null;
}
delegateHook && delegateHook.apply(this);
};
}
}
export function PublicFeature<T>(definition: DirectiveDef<T>) {
definition.diPublic = diPublic;
}
const EMPTY = {};
/** Swaps the keys and values of an object. */
function invertObject(obj: any): any {
if (obj == null) return EMPTY;
const newObj: any = {};
for (let minifiedKey in obj) {
newObj[obj[minifiedKey]] = minifiedKey;
}
return newObj;
}
/**
* Create a directive definition object.
*
* # Example
* ```
* class MyDirective {
* // Generated by Angular Template Compiler
* // [Symbol] syntax will not be supported by TypeScript until v2.7
* static ngDirectiveDef = defineDirective({
* ...
* });
* }
* ```
*/
export const defineDirective = defineComponent as any as<T>(directiveDefinition: {
/**
* Directive type, needed to configure the injector.
*/
type: Type<T>;
/** The selectors that will be used to match nodes to this directive. */
selectors: CssSelectorList;
/**
* Factory method used to create an instance of directive.
*/
factory: () => T | ({0: T} & any[]); /* trying to say T | [T, ...any] */
/**
* Static attributes to set on host element.
*
* Even indices: attribute name
* Odd indices: attribute value
*/
attributes?: string[];
/**
* A map of input names.
*
* The format is in: `{[actualPropertyName: string]:string}`.
*
* Which the minifier may translate to: `{[minifiedPropertyName: string]:string}`.
*
* This allows the render to re-construct the minified and non-minified names
* of properties.
*/
inputs?: {[P in keyof T]?: string};
/**
* A map of output names.
*
* The format is in: `{[actualPropertyName: string]:string}`.
*
* Which the minifier may translate to: `{[minifiedPropertyName: string]:string}`.
*
* This allows the render to re-construct the minified and non-minified names
* of properties.
*/
outputs?: {[P in keyof T]?: string};
/**
* A list of optional features to apply.
*
* See: {@link NgOnChangesFeature}, {@link PublicFeature}
*/
features?: DirectiveDefFeature[];
/**
* Function executed by the parent template to allow child directive to apply host bindings.
*/
hostBindings?: (directiveIndex: number, elementIndex: number) => void;
/**
* Defines the name that can be used in the template to assign this directive to a variable.
*
* See: {@link Directive.exportAs}
*/
exportAs?: string;
}) => DirectiveDef<T>;
/**
* Create a pipe definition object.
*
* # Example
* ```
* class MyPipe implements PipeTransform {
* // Generated by Angular Template Compiler
* static ngPipeDef = definePipe({
* ...
* });
* }
* ```
* @param pipeDef Pipe definition generated by the compiler
*/
export function definePipe<T>(pipeDef: {
/** Name of the pipe. Used for matching pipes in template to pipe defs. */
name: string,
/** Pipe class reference. Needed to extract pipe lifecycle hooks. */
type: Type<T>,
/** A factory for creating a pipe instance. */
factory: () => T,
/** Whether the pipe is pure. */
pure?: boolean
}): PipeDef<T> {
return <PipeDef<T>>{
name: pipeDef.name,
n: pipeDef.factory,
pure: pipeDef.pure !== false,
onDestroy: pipeDef.type.prototype.ngOnDestroy || null
};
}