/** * @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 }; }