/** * @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 {ChangeDetectionStrategy} from '../change_detection/constants'; import {Mutable, Type} from '../interface/type'; import {NgModuleDef} from '../metadata/ng_module'; import {SchemaMetadata} from '../metadata/schema'; import {ViewEncapsulation} from '../metadata/view'; import {noSideEffects} from '../util/closure'; import {initNgDevMode} from '../util/ng_dev_mode'; import {stringify} from '../util/stringify'; import {EMPTY_ARRAY, EMPTY_OBJ} from './empty'; import {NG_COMP_DEF, NG_DIR_DEF, NG_FACTORY_DEF, NG_LOC_ID_DEF, NG_MOD_DEF, NG_PIPE_DEF} from './fields'; import {ComponentDef, ComponentDefFeature, ComponentTemplate, ComponentType, ContentQueriesFunction, DirectiveDef, DirectiveDefFeature, DirectiveTypesOrFactory, FactoryFn, HostBindingsFunction, PipeDef, PipeType, PipeTypesOrFactory, ViewQueriesFunction} from './interfaces/definition'; import {AttributeMarker, TAttributes, TConstants} from './interfaces/node'; import {CssSelectorList, SelectorFlags} from './interfaces/projection'; import {NgModuleType} from './ng_module_ref'; let _renderCompCount = 0; // While these types are unused here, they are required so that types don't // get resolved lazily. see: https://github.com/Microsoft/web-build-tools/issues/1050 type _web_build_tools_issue_1050_SelectorFlags = SelectorFlags; type _web_build_tools_issue_1050_AttributeMarker = AttributeMarker; /** * 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 ɵcmp = defineComponent({ * ... * }); * } * ``` * @codeGenApi */ export function ɵɵdefineComponent(componentDefinition: { /** * Directive type, needed to configure the injector. */ type: Type; /** The selectors that will be used to match nodes to this component. */ selectors?: CssSelectorList; /** * The number of nodes, local refs, and pipes in this component template. * * Used to calculate the length of this component's LView array, so we * can pre-fill the array and set the binding start index. */ // TODO(kara): remove queries from this count decls: number; /** * The number of bindings in this component template (including pure fn bindings). * * Used to calculate the length of this component's LView array, so we * can pre-fill the array and set the host binding start index. */ vars: number; /** * A map of input names. * * The format is in: `{[actualPropertyName: string]:(string|[string, string])}`. * * Given: * ``` * class MyComponent { * @Input() * publicInput1: string; * * @Input('publicInput2') * declaredInput2: string; * } * ``` * * is described as: * ``` * { * publicInput1: 'publicInput1', * declaredInput2: ['publicInput2', 'declaredInput2'], * } * ``` * * Which the minifier may translate to: * ``` * { * minifiedPublicInput1: 'publicInput1', * minifiedDeclaredInput2: ['publicInput2', 'declaredInput2'], * } * ``` * * This allows the render to re-construct the minified, public, and declared names * of properties. * * NOTE: * - Because declared and public name are usually same we only generate the array * `['public', 'declared']` format when they differ. * - The reason why this API and `outputs` API is not the same is that `NgOnChanges` has * inconsistent behavior in that it uses declared names rather than minified or public. For * this reason `NgOnChanges` will be deprecated and removed in future version and this * API will be simplified to be consistent with `output`. */ inputs?: {[P in keyof T]?: string | [string, 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?: HostBindingsFunction; /** * The number of bindings in this directive `hostBindings` (including pure fn bindings). * * Used to calculate the length of the component's LView array, so we * can pre-fill the array and set the host binding start index. */ hostVars?: number; /** * Assign static attribute values to a host element. * * This property will assign static attribute values as well as class and style * values to a host element. Since attribute values can consist of different types of values, the * `hostAttrs` array must include the values in the following format: * * attrs = [ * // static attributes (like `title`, `name`, `id`...) * attr1, value1, attr2, value, * * // a single namespace value (like `x:id`) * NAMESPACE_MARKER, namespaceUri1, name1, value1, * * // another single namespace value (like `x:name`) * NAMESPACE_MARKER, namespaceUri2, name2, value2, * * // a series of CSS classes that will be applied to the element (no spaces) * CLASSES_MARKER, class1, class2, class3, * * // a series of CSS styles (property + value) that will be applied to the element * STYLES_MARKER, prop1, value1, prop2, value2 * ] * * All non-class and non-style attributes must be defined at the start of the list * first before all class and style values are set. When there is a change in value * type (like when classes and styles are introduced) a marker must be used to separate * the entries. The marker values themselves are set via entries found in the * [AttributeMarker] enum. */ hostAttrs?: TAttributes; /** * Function to create instances of content queries associated with a given directive. */ contentQueries?: ContentQueriesFunction; /** * 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(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; /** * Constants for the nodes in the component's view. * Includes attribute arrays, local definition arrays etc. */ consts?: TConstants; /** * An array of `ngContent[selector]` values that were found in the template. */ ngContentSelectors?: string[]; /** * Additional set of instructions specific to view query processing. This could be seen as a * set of instruction to be inserted into the template function. * * Query-related instructions need to be pulled out to a specific function as a timing of * execution is different as compared to all other instructions (after change detection hooks but * before view hooks). */ viewQuery?: ViewQueriesFunction| null; /** * A list of optional features to apply. * * See: {@link NgOnChangesFeature}, {@link ProvidersFeature} */ features?: ComponentDefFeature[]; /** * Defines template and style encapsulation options available for Component's {@link Component}. */ encapsulation?: ViewEncapsulation; /** * Defines arbitrary developer-defined data to be stored on a renderer instance. * This is useful for renderers that delegate to other renderers. * * see: animation */ data?: {[kind: string]: any}; /** * A set of styles that the component needs to be present for component to render correctly. */ styles?: string[]; /** * The strategy that the default change detector uses to detect changes. * When set, takes effect the next time change detection is triggered. */ changeDetection?: ChangeDetectionStrategy; /** * 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; /** * The set of schemas that declare elements to be allowed in the component's template. */ schemas?: SchemaMetadata[] | null; }): never { return noSideEffects(() => { // Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent. // See the `initNgDevMode` docstring for more information. (typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode(); const type = componentDefinition.type; const typePrototype = type.prototype; const declaredInputs: {[key: string]: string} = {} as any; const def: Mutable, keyof ComponentDef> = { type: type, providersResolver: null, decls: componentDefinition.decls, vars: componentDefinition.vars, factory: null, template: componentDefinition.template || null !, consts: componentDefinition.consts || null, ngContentSelectors: componentDefinition.ngContentSelectors, hostBindings: componentDefinition.hostBindings || null, hostVars: componentDefinition.hostVars || 0, hostAttrs: componentDefinition.hostAttrs || null, contentQueries: componentDefinition.contentQueries || null, declaredInputs: declaredInputs, inputs: null !, // assigned in noSideEffects outputs: null !, // assigned in noSideEffects exportAs: componentDefinition.exportAs || null, onChanges: null, onInit: typePrototype.ngOnInit || null, doCheck: typePrototype.ngDoCheck || null, afterContentInit: typePrototype.ngAfterContentInit || null, afterContentChecked: typePrototype.ngAfterContentChecked || null, afterViewInit: typePrototype.ngAfterViewInit || null, afterViewChecked: typePrototype.ngAfterViewChecked || null, onDestroy: typePrototype.ngOnDestroy || null, onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush, directiveDefs: null !, // assigned in noSideEffects pipeDefs: null !, // assigned in noSideEffects selectors: componentDefinition.selectors || EMPTY_ARRAY, viewQuery: componentDefinition.viewQuery || null, features: componentDefinition.features as DirectiveDefFeature[] || null, data: componentDefinition.data || {}, // TODO(misko): convert ViewEncapsulation into const enum so that it can be used directly in // the next line. Also `None` should be 0 not 2. encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated, id: 'c', styles: componentDefinition.styles || EMPTY_ARRAY, _: null as never, setInput: null, schemas: componentDefinition.schemas || null, tView: null, }; const directiveTypes = componentDefinition.directives !; const feature = componentDefinition.features; const pipeTypes = componentDefinition.pipes !; def.id += _renderCompCount++; def.inputs = invertObject(componentDefinition.inputs, declaredInputs), def.outputs = invertObject(componentDefinition.outputs), feature && feature.forEach((fn) => fn(def)); def.directiveDefs = directiveTypes ? () => (typeof directiveTypes === 'function' ? directiveTypes() : directiveTypes) .map(extractDirectiveDef) : null; def.pipeDefs = pipeTypes ? () => (typeof pipeTypes === 'function' ? pipeTypes() : pipeTypes).map(extractPipeDef) : null; return def as never; }); } /** * @codeGenApi */ export function ɵɵsetComponentScope( type: ComponentType, directives: Type[], pipes: Type[]): void { const def = (type.ɵcmp as ComponentDef); def.directiveDefs = () => directives.map(extractDirectiveDef); def.pipeDefs = () => pipes.map(extractPipeDef); } export function extractDirectiveDef(type: Type): DirectiveDef|ComponentDef { const def = getComponentDef(type) || getDirectiveDef(type); if (ngDevMode && !def) { throw new Error(`'${type.name}' is neither 'ComponentType' or 'DirectiveType'.`); } return def !; } export function extractPipeDef(type: Type): PipeDef { const def = getPipeDef(type); if (ngDevMode && !def) { throw new Error(`'${type.name}' is not a 'PipeType'.`); } return def !; } export const autoRegisterModuleById: {[id: string]: NgModuleType} = {}; /** * @codeGenApi */ export function ɵɵdefineNgModule(def: { /** Token representing the module. Used by DI. */ type: T; /** List of components to bootstrap. */ bootstrap?: Type[] | (() => Type[]); /** List of components, directives, and pipes declared by this module. */ declarations?: Type[] | (() => Type[]); /** List of modules or `ModuleWithProviders` imported by this module. */ imports?: Type[] | (() => Type[]); /** * List of modules, `ModuleWithProviders`, components, directives, or pipes exported by this * module. */ exports?: Type[] | (() => Type[]); /** The set of schemas that declare elements to be allowed in the NgModule. */ schemas?: SchemaMetadata[] | null; /** Unique ID for the module that is used with `getModuleFactory`. */ id?: string | null; }): never { const res: NgModuleDef = { type: def.type, bootstrap: def.bootstrap || EMPTY_ARRAY, declarations: def.declarations || EMPTY_ARRAY, imports: def.imports || EMPTY_ARRAY, exports: def.exports || EMPTY_ARRAY, transitiveCompileScopes: null, schemas: def.schemas || null, id: def.id || null, }; if (def.id != null) { noSideEffects( () => { autoRegisterModuleById[def.id !] = def.type as unknown as NgModuleType; }); } return res as never; } /** * Adds the module metadata that is necessary to compute the module's transitive scope to an * existing module definition. * * Scope metadata of modules is not used in production builds, so calls to this function can be * marked pure to tree-shake it from the bundle, allowing for all referenced declarations * to become eligible for tree-shaking as well. * * @codeGenApi */ export function ɵɵsetNgModuleScope(type: any, scope: { /** List of components, directives, and pipes declared by this module. */ declarations?: Type[] | (() => Type[]); /** List of modules or `ModuleWithProviders` imported by this module. */ imports?: Type[] | (() => Type[]); /** * List of modules, `ModuleWithProviders`, components, directives, or pipes exported by this * module. */ exports?: Type[] | (() => Type[]); }): void { return noSideEffects(() => { const ngModuleDef = getNgModuleDef(type, true); ngModuleDef.declarations = scope.declarations || EMPTY_ARRAY; ngModuleDef.imports = scope.imports || EMPTY_ARRAY; ngModuleDef.exports = scope.exports || EMPTY_ARRAY; }) as never; } /** * Inverts an inputs or outputs lookup such that the keys, which were the * minified keys, are part of the values, and the values are parsed so that * the publicName of the property is the new key * * e.g. for * * ``` * class Comp { * @Input() * propName1: string; * * @Input('publicName2') * declaredPropName2: number; * } * ``` * * will be serialized as * * ``` * { * propName1: 'propName1', * declaredPropName2: ['publicName2', 'declaredPropName2'], * } * ``` * * which is than translated by the minifier as: * * ``` * { * minifiedPropName1: 'propName1', * minifiedPropName2: ['publicName2', 'declaredPropName2'], * } * ``` * * becomes: (public name => minifiedName) * * ``` * { * 'propName1': 'minifiedPropName1', * 'publicName2': 'minifiedPropName2', * } * ``` * * Optionally the function can take `secondary` which will result in: (public name => declared name) * * ``` * { * 'propName1': 'propName1', * 'publicName2': 'declaredPropName2', * } * ``` * */ function invertObject( obj?: {[P in keyof T]?: string | [string, string]}, secondary?: {[key: string]: string}): {[P in keyof T]: string} { if (obj == null) return EMPTY_OBJ as any; const newLookup: any = {}; for (const minifiedKey in obj) { if (obj.hasOwnProperty(minifiedKey)) { let publicName: string|[string, string] = obj[minifiedKey] !; let declaredName = publicName; if (Array.isArray(publicName)) { declaredName = publicName[1]; publicName = publicName[0]; } newLookup[publicName] = minifiedKey; if (secondary) { (secondary[publicName] = declaredName as string); } } } return newLookup; } /** * Create a directive definition object. * * # Example * ```ts * class MyDirective { * // Generated by Angular Template Compiler * // [Symbol] syntax will not be supported by TypeScript until v2.7 * static ɵdir = ɵɵdefineDirective({ * ... * }); * } * ``` * * @codeGenApi */ export const ɵɵdefineDirective = ɵɵdefineComponent as any as(directiveDefinition: { /** * Directive type, needed to configure the injector. */ type: Type; /** The selectors that will be used to match nodes to this directive. */ selectors?: CssSelectorList; /** * A map of input names. * * The format is in: `{[actualPropertyName: string]:(string|[string, string])}`. * * Given: * ``` * class MyComponent { * @Input() * publicInput1: string; * * @Input('publicInput2') * declaredInput2: string; * } * ``` * * is described as: * ``` * { * publicInput1: 'publicInput1', * declaredInput2: ['declaredInput2', 'publicInput2'], * } * ``` * * Which the minifier may translate to: * ``` * { * minifiedPublicInput1: 'publicInput1', * minifiedDeclaredInput2: [ 'publicInput2', 'declaredInput2'], * } * ``` * * This allows the render to re-construct the minified, public, and declared names * of properties. * * NOTE: * - Because declared and public name are usually same we only generate the array * `['declared', 'public']` format when they differ. * - The reason why this API and `outputs` API is not the same is that `NgOnChanges` has * inconsistent behavior in that it uses declared names rather than minified or public. For * this reason `NgOnChanges` will be deprecated and removed in future version and this * API will be simplified to be consistent with `output`. */ inputs?: {[P in keyof T]?: string | [string, 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 ProvidersFeature}, {@link InheritDefinitionFeature} */ features?: DirectiveDefFeature[]; /** * Function executed by the parent template to allow child directive to apply host bindings. */ hostBindings?: HostBindingsFunction; /** * The number of bindings in this directive `hostBindings` (including pure fn bindings). * * Used to calculate the length of the component's LView array, so we * can pre-fill the array and set the host binding start index. */ hostVars?: number; /** * Assign static attribute values to a host element. * * This property will assign static attribute values as well as class and style * values to a host element. Since attribute values can consist of different types of values, the * `hostAttrs` array must include the values in the following format: * * attrs = [ * // static attributes (like `title`, `name`, `id`...) * attr1, value1, attr2, value, * * // a single namespace value (like `x:id`) * NAMESPACE_MARKER, namespaceUri1, name1, value1, * * // another single namespace value (like `x:name`) * NAMESPACE_MARKER, namespaceUri2, name2, value2, * * // a series of CSS classes that will be applied to the element (no spaces) * CLASSES_MARKER, class1, class2, class3, * * // a series of CSS styles (property + value) that will be applied to the element * STYLES_MARKER, prop1, value1, prop2, value2 * ] * * All non-class and non-style attributes must be defined at the start of the list * first before all class and style values are set. When there is a change in value * type (like when classes and styles are introduced) a marker must be used to separate * the entries. The marker values themselves are set via entries found in the * [AttributeMarker] enum. */ hostAttrs?: TAttributes; /** * Function to create instances of content queries associated with a given directive. */ contentQueries?: ContentQueriesFunction; /** * Additional set of instructions specific to view query processing. This could be seen as a * set of instructions to be inserted into the template function. */ viewQuery?: ViewQueriesFunction| null; /** * Defines the name that can be used in the template to assign this directive to a variable. * * See: {@link Directive.exportAs} */ exportAs?: string[]; }) => never; /** * Create a pipe definition object. * * # Example * ``` * class MyPipe implements PipeTransform { * // Generated by Angular Template Compiler * static ɵpipe = definePipe({ * ... * }); * } * ``` * @param pipeDef Pipe definition generated by the compiler * * @codeGenApi */ export function ɵɵdefinePipe(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, /** Whether the pipe is pure. */ pure?: boolean }): never { return (>{ type: pipeDef.type, name: pipeDef.name, factory: null, pure: pipeDef.pure !== false, onDestroy: pipeDef.type.prototype.ngOnDestroy || null }) as never; } /** * The following getter methods retrieve the definition form the type. Currently the retrieval * honors inheritance, but in the future we may change the rule to require that definitions are * explicit. This would require some sort of migration strategy. */ export function getComponentDef(type: any): ComponentDef|null { return type[NG_COMP_DEF] || null; } export function getDirectiveDef(type: any): DirectiveDef|null { return type[NG_DIR_DEF] || null; } export function getPipeDef(type: any): PipeDef|null { return type[NG_PIPE_DEF] || null; } export function getFactoryDef(type: any, throwNotFound: true): FactoryFn; export function getFactoryDef(type: any): FactoryFn|null; export function getFactoryDef(type: any, throwNotFound?: boolean): FactoryFn|null { const hasFactoryDef = type.hasOwnProperty(NG_FACTORY_DEF); if (!hasFactoryDef && throwNotFound === true && ngDevMode) { throw new Error(`Type ${stringify(type)} does not have 'ɵfac' property.`); } return hasFactoryDef ? type[NG_FACTORY_DEF] : null; } export function getNgModuleDef(type: any, throwNotFound: true): NgModuleDef; export function getNgModuleDef(type: any): NgModuleDef|null; export function getNgModuleDef(type: any, throwNotFound?: boolean): NgModuleDef|null { const ngModuleDef = type[NG_MOD_DEF] || null; if (!ngModuleDef && throwNotFound === true) { throw new Error(`Type ${stringify(type)} does not have 'ɵmod' property.`); } return ngModuleDef; } export function getNgLocaleIdDef(type: any): string|null { return (type as any)[NG_LOC_ID_DEF] || null; }